Le pipeline de sécurité¶
Chaque paquet .deb qui entre dans Repod traverse un pipeline de validation en six étapes avant de devenir disponible pour apt install. Cette page explique la logique de conception derrière le pipeline, ce que chaque étape vérifie réellement, et ce que signifient les modes d'échec — pas comment le configurer, mais pourquoi il est structuré ainsi.
Un seul pipeline, chaque format
Les exemples ci-dessous utilisent .deb comme référence, mais le
scan antivirus et l'analyse CVE (étapes 3–4) s'exécutent sans
modification contre .rpm, .apk, Maven, PyPI, npm, et les
artefacts d'image de conteneur également — chaque format traverse
le même verrou obligatoire avant publication. Voir
Formats de paquets pour savoir en quoi les
étapes de métadonnées/checksum/index propres à chaque format
diffèrent de celles d'APT.
Pourquoi un pipeline ?¶
L'approche traditionnelle des dépôts APT privés est « uploader et servir ». Un développeur exécute reprepro includedeb jammy mypackage.deb et le paquet est immédiatement disponible pour chaque machine pointant vers le dépôt. Cela fonctionne bien pour de petites équipes avec un niveau de confiance élevé. C'est un risque important pour la chaîne d'approvisionnement dans tous les autres cas.
Les attaques de la chaîne d'approvisionnement contre les dépôts de paquets sont devenues l'un des vecteurs les plus efficaces pour compromettre une infrastructure à grande échelle. L'attaquant n'a pas besoin de compromettre directement les systèmes cibles — il compromet le paquet déployé sur ces systèmes. Une dépendance malveillante, un binaire trojanisé, un paquet portant une CVE connue mais non corrigée : tout cela peut être introduit via le chemin d'upload si ce chemin n'a aucune validation.
Le pipeline de Repod applique un contrat de validation obligatoire : aucun paquet ne peut contourner les vérifications. Il n'y a pas de « chemin rapide », pas de contournement admin qui saute des étapes, et aucun mécanisme pour promouvoir un fichier mis en quarantaine directement vers l'arborescence APT sans qu'il passe par le pipeline. Ce n'est pas un accident — c'est documenté comme une fonctionnalité dans la section sur le contournement du pipeline ci-dessous.
Le pipeline est synchrone pour les uploads individuels (l'appel API bloque jusqu'à la fin de la validation) et s'exécute en ligne pour chaque import depuis des sources externes. Cela signifie que la latence de validation est directement visible pour l'appelant, ce qui crée une pression naturelle pour garder les outils rapides et les politiques bien réglées.
Vue d'ensemble du pipeline¶
flowchart TD
Upload["📦 .deb reçu\n/repos/staging/incoming/"]
Upload --> S1["Étape 1 — Validation du format\ndpkg-deb --info"]
S1 -->|Échec| R1["❌ Rejeté\ndéplacé vers quarantine/"]
S1 -->|Réussi| S2["Étape 2 — Provenance SHA-256\nvs index Packages.gz"]
S2 -->|Échec| R2["❌ Rejeté\n(fichier altéré)"]
S2 -->|Réussi| S3["Étape 3 — Scan antivirus\nclamd (INSTREAM)"]
S3 -->|Virus trouvé| R3["❌ Rejeté + mis en quarantaine"]
S3 -->|Propre| S4["Étape 4 — Analyse CVE\n+ enrichissement EPSS/KEV"]
S4 -->|politique block| R4["❌ Rejeté\ncve_status: blocked"]
S4 -->|politique review| PR["⏳ En attente de revue\ndéplacé vers pool/\nstatus: pending_review"]
S4 -->|warn / allow| S5["Étape 5 — Signature GPG\ngpg --verify"]
S5 -->|Signature invalide| R5["❌ Rejeté"]
S5 -->|Valide / absente| S6["Étape 6 — Vérification des dépendances\ncontre pool/"]
S6 -->|Dépendances manquantes| W6["⚠️ Avertissement\n(non bloquant par défaut)"]
S6 -->|Toutes présentes| Indexed["✅ Indexé\nreprepro → dists/\nstatus: indexed"]
W6 --> Indexed
style R1 fill:#fdd,stroke:#c00
style R2 fill:#fdd,stroke:#c00
style R3 fill:#fdd,stroke:#c00
style R4 fill:#fdd,stroke:#c00
style R5 fill:#fdd,stroke:#c00
style PR fill:#ffd,stroke:#aa0
style W6 fill:#fff3cd,stroke:#856404
style Indexed fill:#d4edda,stroke:#155724
Enrichissement EPSS + KEV
Les scores EPSS et les flags CISA KEV sont récupérés pendant l'étape 4 (analyse CVE) et utilisés immédiatement par le moteur de politique. L'enrichissement n'est pas un verrou séparé — il s'exécute en ligne dans le cadre du scan CVE et ses résultats sont intégrés dans le manifeste et l'affichage de la file de revue.
Décomposition étape par étape¶
Le pipeline est implémenté dans run_validation_pipeline(). Chaque étape s'exécute dans l'ordre ; certaines court-circuitent en cas d'échec. Les étapes peuvent être désactivées individuellement dans settings.json (clés clamav_scan et grype_scan sous validation).
Étape 1 — Validation du format¶
Outil : dpkg-deb --info
Le fichier est d'abord vérifié comme étant un paquet Debian structurellement valide. dpkg-deb --info parse l'archive de contrôle du paquet, extrait le fichier control, et rapporte le nom, la version, l'architecture et les dépendances déclarées du paquet. Si le fichier est tronqué, corrompu, ou n'est pas du tout un .deb, cette commande retourne un code de sortie non nul.
En cas d'échec : Le fichier est immédiatement déplacé vers /repos/staging/quarantine/. Aucune étape ultérieure ne s'exécute. Le pipeline court-circuite en cas d'échec de format car les outils suivants (ClamAV, Grype) attendent une entrée .deb valide.
Pourquoi c'est important : Cela empêche les fichiers malformés de gaspiller du temps de scan et — plus important encore — de déclencher un comportement inattendu dans les outils de scan. Des outils comme ClamAV et Grype sont connus pour se comporter de manière imprévisible sur une entrée malformée. Rejeter tôt évite cette surface.
Étape 2 — Provenance SHA-256¶
Outil : hashlib.sha256 de Python + comparaison contre l'index amont Packages.gz
Lorsqu'un paquet est importé depuis une source amont (un miroir de sécurité, une archive Ubuntu), Repod enregistre le SHA-256 attendu depuis le fichier Packages.gz de cette source, au moment de l'import. Pendant la validation, le SHA-256 réel du fichier est recalculé et comparé à cette référence.
Pour les paquets uploadés manuellement (où aucune référence amont n'existe), cette étape réussit avec une note indiquant que la provenance n'est pas vérifiable. Le checksum est tout de même calculé et enregistré dans le manifeste pour un audit ultérieur.
En cas d'échec : Rejet immédiat. Une incohérence de SHA-256 signifie que le fichier tel que reçu ne correspond pas à ce que la source amont a publié. C'est la défense principale contre les attaques de type homme-du-milieu sur le transit des paquets.
Pourquoi c'est important : TLS protège le transit entre votre serveur et le miroir amont, mais ne protège pas contre un miroir compromis servant un fichier différent au même chemin. Le SHA-256 issu de Packages.gz (lui-même signé par la clé GPG amont) fournit un signal d'intégrité indépendant.
Étape 3 — Scan antivirus¶
Outil : démon clamd via le protocole INSTREAM sur un socket Unix, avec la base de signatures quotidienne de ClamAV dans /var/lib/clamav/
Le fichier .deb entier — y compris son archive de données embarquée — est scanné à la recherche de signatures de malware connues à l'aide du démon clamd. Les signatures (~700 Mo) sont chargées une fois au démarrage du conteneur et conservées en mémoire — elles ne sont pas rechargées pour chaque scan individuel. Cela évite la surcharge répétée de chargement de base de données liée à l'appel direct de clamscan et prévient les conditions OOM qui surviennent lorsque clamscan est utilisé comme sous-processus sous des limites de mémoire strictes. Le système bascule vers clamscan uniquement si le démon clamd est indisponible.
La base de données daily.cld de ClamAV couvre des dizaines de milliers de familles de malware, portes dérobées, et kits d'exploitation. Les mises à jour de signatures sont gérées automatiquement par freshclam toutes les 12 heures ; une mise à jour manuelle est également disponible via l'interface (Paramètres → ClamAV → Mettre à jour les signatures).
En cas d'échec (virus détecté) : Le démon retourne le nom de la menace via la réponse INSTREAM. Le fichier est déplacé en quarantaine et une entrée d'audit FAILURE est écrite. Le nom de la menace est enregistré à la fois dans le journal d'audit et dans le résultat de l'étape du manifeste.
En cas d'erreur d'outil : Si clamd est indisponible et que le repli sur clamscan échoue également, l'étape est enregistrée comme un avertissement mais ne bloque pas le pipeline. Cela évite qu'un échec de mise à jour de la base antivirus n'arrête tous les uploads.
Pourquoi c'est important : Un malware embarqué dans des paquets .deb est un vecteur d'attaque connu. Le script maintainer postinst s'exécute en tant que root pendant l'installation ; un paquet trojanisé peut exécuter du code arbitraire sur chaque machine qui l'installe. Le scan antivirus attrape les menaces connues avant qu'elles n'entrent dans la chaîne de distribution.
Étape 4 — Analyse CVE¶
Outil : scanner CVE avec les sources NVD, GitHub Advisory Database, et CISA KEV
Le scanner analyse le contenu du .deb — les fichiers installés, les bibliothèques et leurs versions — contre sa base de données de vulnérabilités. Le flag --distro est passé avec le codename de la distribution cible (jammy, bookworm, etc.) pour améliorer la précision de correspondance selon les correctifs spécifiques à la distribution. La table de correspondance de distribution utilisée est :
| Codename APT | Distro du scanner |
|---|---|
focal |
ubuntu:20.04 |
jammy |
ubuntu:22.04 |
noble |
ubuntu:24.04 |
buster |
debian:10 |
bullseye |
debian:11 |
bookworm |
debian:12 |
Le scanner produit du JSON structuré avec une entrée par correspondance : ID CVE, sévérité, score CVSS, composant affecté, état de correction, et versions de correction. Repod traite cette sortie, enrichit les résultats avec les données EPSS et CISA KEV (si auto_enrich est activé), et applique la politique CVE de settings.json.
Application de la politique CVE : La section cve_policy fait correspondre chaque sévérité à l'une de quatre actions. Le résultat le plus restrictif parmi toutes les correspondances l'emporte :
| Action | Effet |
|---|---|
block |
Le pipeline échoue ; cve_status: blocked ; fichier mis en quarantaine |
review |
Le pipeline réussit ; paquet stocké avec status: pending_review ; non publié vers APT |
warn |
Le paquet continue ; avertissement enregistré dans le manifeste et l'interface |
allow |
La CVE est enregistrée, aucune action |
Si la politique block est déclenchée : cve_status est positionné à blocked, result.passed devient False, et le pipeline court-circuite. Le fichier est mis en quarantaine.
Si la politique review est déclenchée : cve_status est positionné à pending_review. Le pipeline n'échoue pas (le fichier n'est pas mis en quarantaine), mais le paquet est stocké avec status: pending_review et retenu hors de l'index APT. Voir Workflow de revue CVE.
Enrichissement EPSS + KEV (en ligne) : Pour chaque CVE trouvée, deux signaux supplémentaires sont récupérés depuis des API externes et mis en cache sur disque (TTL de 24 heures) : - Score EPSS — probabilité d'exploitation dans les 30 prochains jours (FIRST.org) - Flag CISA KEV — si cette CVE est activement exploitée (catalogue CISA)
L'enrichissement est non bloquant : si les API externes sont injoignables et que le cache est périmé, les CVE sont enregistrées sans enrichissement plutôt que de bloquer l'upload.
Timeout du scanner : 300 secondes. Si le scan dépasse cette limite, l'étape est enregistrée comme un avertissement et le pipeline continue. Cela évite qu'un scan lent ou bloqué ne retienne indéfiniment les uploads.
Pourquoi c'est important : Le scan CVE au moment de l'upload attrape les vulnérabilités connues avant qu'elles n'atteignent la production. Les signaux EPSS et KEV permettent au moteur de politique de distinguer une vulnérabilité théorique d'une vulnérabilité activement exploitée dans la nature.
Étape 5 — Vérification de la signature GPG¶
Outil : gpg --verify contre le trousseau partagé dans /repos/gnupg
Si un fichier .sig ou .asc est présent aux côtés du .deb uploadé (même nom de fichier avec un suffixe de signature), Repod le vérifie contre les clés du trousseau GPG. Si aucun fichier de signature n'est présent, cette étape réussit avec une note indiquant qu'aucune signature n'a été fournie.
La vérification GPG est actuellement optionnelle par conception : tous les paquets dans la nature ne portent pas une signature détachée, et en exiger une rendrait impossible l'upload de paquets depuis des sources qui ne signent pas leurs fichiers individuels (par opposition à la signature des métadonnées de leur dépôt). Cela pourrait être rendu plus strict via configuration dans de futures versions.
En cas d'échec : Une signature présente mais invalide est un échec strict. Une signature absente est un succès souple avec une note.
Pourquoi c'est important : Pour les paquets importés depuis des sources internes fiables, une signature GPG fournit une garantie de chaîne de custody : le binaire a été produit par la clé attendue. Une signature invalide sur un fichier qui prétendait être signé est un signal plus fort qu'une signature manquante sur un fichier qui n'en revendiquait aucune.
Étape 6 — Résolution des dépendances (étape finale)¶
Outil : dpkg-deb -f <file> Depends + recherche de nom de fichier dans /repos/pool/
Le champ Depends: du fichier de contrôle du paquet est parsé en une liste structurée de noms de paquets (avec des contraintes de version optionnelles). Chaque dépendance déclarée est vérifiée contre le contenu de pool/ — spécifiquement, si un .deb au nom correspondant existe.
En cas d'échec (dépendances manquantes) : Par défaut, il s'agit d'un avertissement non bloquant. L'étape est enregistrée comme échouée mais strict_deps=False signifie que le résultat global du pipeline reste passed: true. Les dépendances manquantes sont enregistrées dans le champ deps_missing du manifeste, exposé dans l'interface.
Si strict_deps=True est passé (pas la valeur par défaut pour les uploads), les dépendances manquantes deviennent un échec strict.
Pourquoi c'est important : Un paquet déclarant des dépendances absentes du dépôt local échouera à l'installation avec apt install. Cette vérification révèle ce problème avant que le paquet ne soit déployé, donnant à l'opérateur l'opportunité d'importer d'abord les dépendances manquantes. Dans les environnements air-gap, ceci est particulièrement précieux car il n'y a aucun repli vers un miroir amont.
Configuration de la politique CVE¶
Le tableau de politique ci-dessous montre quelles actions sont disponibles à chaque niveau de sévérité et ce que Repod recommande comme point de départ.
| Sévérité | Actions disponibles | Défaut recommandé | Justification |
|---|---|---|---|
critical |
block, review, warn, allow |
block |
Les CVE critiques ont un score de base CVSS ≥ 9,0. Combiné à la prévalence CISA KEV à ce niveau, le blocage automatique est approprié. |
high |
block, review, warn, allow |
review |
Les CVE élevées (7,0–8,9) sont significatives mais peuvent avoir des facteurs atténuants. Une revue humaine est appropriée. |
medium |
block, review, warn, allow |
warn |
Les CVE moyennes sont courantes dans les logiciels de production. Le blocage automatique crée trop de friction ; un avertissement visible est suffisant. |
low |
block, review, warn, allow |
allow |
Les CVE faibles ont un impact pratique minimal et des taux élevés de faux positifs à cette sévérité. Les enregistrer est utile ; agir automatiquement sur elles ne l'est pas. |
negligible |
block, review, warn, allow |
allow |
Informatif uniquement. |
Interaction entre la politique et le CISA KEV
Même si la politique de niveau de sévérité d'une CVE est warn ou allow, la présence d'un flag CISA KEV est mise en avant de manière visible dans la file de revue et le manifeste. Une CVE marquée KEV à sévérité Moyenne (pilotée par l'EPSS) peut être escaladée manuellement indépendamment de la décision automatique du moteur de politique.
Statuts des paquets¶
| Statut | Signification | Qui peut le voir | Prochaine action |
|---|---|---|---|
indexed |
Le paquet a passé toutes les vérifications et est disponible via apt install |
Tous les utilisateurs authentifiés ; clients APT | Utilisation normale ; surveillance des nouvelles CVE |
pending_review |
Le paquet a passé les vérifications de format/AV/intégrité mais présente des CVE nécessitant une approbation humaine | admin, maintainer, auditor dans l'interface ; non visible pour les clients APT |
Le RSSI/admin examine les détails CVE et approuve ou rejette |
quarantined |
Le paquet a été rejeté par le pipeline ou par une décision humaine | admin, maintainer, auditor dans l'interface uniquement |
Investiguer la raison du rejet ; re-uploader une version corrigée |
rejected |
Rejet humain explicite depuis la file de revue (distinct de la mise en quarantaine automatique) | admin, maintainer, auditor |
Fichier déplacé vers staging/quarantine/ ; décision enregistrée de manière permanente |
Performance¶
Le temps d'exécution du pipeline est dominé par le scan CVE (Grype) et le scan antivirus (ClamAV). Temps typiques sur un serveur modeste :
| Étape | Durée typique |
|---|---|
| Validation du format | < 1 seconde |
| Provenance SHA-256 | < 1 seconde |
| Scan antivirus | 3–15 secondes selon la taille du paquet |
| Analyse CVE (y compris l'enrichissement EPSS/KEV) | 15–95 secondes selon la complexité du paquet, l'état du cache de base de données, et la latence réseau |
| Vérification GPG | < 1 seconde |
| Vérification des dépendances | < 2 secondes |
Total : typiquement 20–115 secondes par paquet.
Pour les imports en masse de grande ampleur (le job de synchronisation de sécurité qui récupère les paquets depuis des miroirs amont), la validation s'exécute séquentiellement, paquet par paquet, au sein d'un même job de synchronisation. Le planificateur est conçu pour s'exécuter de nuit (03:00 par défaut) spécifiquement pour éviter de rivaliser avec le trafic d'upload interactif. Les requêtes d'upload individuelles sont limitées à 20 par minute et par client.
Contourner le pipeline¶
Il n'existe aucun moyen de contourner le pipeline sans modifier le code source.
Il n'y a pas de flag --skip-validation, pas d'endpoint API admin qui promeut un fichier directement, et pas de variable d'environnement qui désactive des étapes. Le pipeline est invoqué de manière inconditionnelle dans run_validation_pipeline(), appelé à la fois par le routeur d'upload et le routeur d'import avant que tout fichier ne soit déplacé vers pool/.
C'est une fonctionnalité, pas une limitation.
La valeur du pipeline de sécurité réside dans son caractère inconditionnel. Un mécanisme de contournement — même restreint au rôle admin — crée un chemin d'exception de politique qui peut être abusé, qui doit être audité séparément, et qui érode le discours de conformité (« tous les paquets sont validés avant d'être servis »). La réponse à « nous devons déployer ceci en urgence et le scanner est lent » est de régler le pipeline (configurer une politique appropriée, pré-peupler la base de données Grype) plutôt que d'ajouter un raccourci qui le contourne.
Déploiement d'urgence
Si un paquet doit être déployé avant que le pipeline ne puisse se terminer normalement — par exemple, un correctif zero-day avec une CVE à EPSS élevé connue dans une dépendance — le chemin correct est : uploader le paquet (il entre en pending_review), puis utiliser la file de revue RSSI pour l'approuver immédiatement avec une justification documentée. Ceci préserve le journal d'audit et la séparation des devoirs, tout en débloquant le déploiement.