Cet article documente un projet étudiant de Master 1 réalisé en 2023–2024, rédigé en décembre 2024. Il offre une rétrospective technique honnête incluant les décisions d’architecture, les défis d’implémentation, la dette technique et les leçons apprises.
Rédigé en décembre 2024
Équipe : 4 étudiants (1× Rust backend, 2× infra/Kubernetes, 1× frontend/docs (moi))
Durée effective : ≈9 mois partagés avec cours et autres projets
Statut actuel : Proof-of-Concept fonctionnel en démo locale, non maintenu en production
1. Le vrai problème que l’on voulait résoudre
Dans la majorité des organisations qui utilisent Harbor comme registre privé :
- Les développeurs (ou les pipelines CI) poussent des images directement
- Le scan de vulnérabilités intégré (Trivy ou Clair) se déclenche souvent après le push
- Il n’existe généralement aucune détection systématique de malware statique
- L’analyse dynamique (runtime) est quasi inexistante avant admission
- Il n’y a pas de point de contrôle humain obligatoire pour les images critiques
- Les refus ne sont pas tracés de façon centralisée et exploitable
Objectifs que nous nous étions fixés :
- Imposer une demande explicite + approbation humaine avant toute admission dans un projet sensible
- Cumuler plusieurs couches d’analyse : vulnérabilités + malware statique + comportement runtime
- Centraliser les résultats et la décision dans l’interface Harbor existante
- Garder le système suffisamment léger pour rester réaliste en contexte étudiant
2. Architecture statique

3. Workflow détaillé (user)

sequenceDiagram
participant Dev
participant UI as Harbor UI + Dockdock-Go
participant API
participant Harbor
participant K8s
participant Dyn as Dynamic Service
participant DB
participant Analyst
Dev->>UI: Soumet demande d'admission<br>image:tag · source · projet cible
UI->>+API: POST /api/v1/requests {…}
API->>Harbor: Réplication artifact → projet temporaire
Note over API,Harbor: Opération asynchrone → polling statut
API->>K8s: Crée Job YaraHunter avec image:tag
K8s-->>API: Logs → PVC → API lit JSON/texte
API->>Harbor: Déclenche scan → polling (5 s) jusqu'à 90 s max
loop Polling résultats scan
Harbor-->>API: Rapport Trivy complet
end
API->>Dyn: POST /analyze {image, tag, rules}
Dyn-->>API: {flags runtime, logs?}
API->>DB: INSERT request + analyses + flags<br>status = PENDING
API-->>UI: 201 Created + refresh liste
loop Décision humaine (minutes à jours)
Analyst->>UI: Liste demandes en attente
Analyst->>UI: Ouvre détails → flags agrégés + rapports bruts
Analyst->>UI: PATCH /requests/{id} {status: APPROVED|REJECTED, comment?}
end
alt APPROVED
API->>Harbor: Réplication finale temp → projet cible
API->>DB: UPDATE status = ACCEPTED, approved_at = now()
else REJECTED
API->>DB: UPDATE status = REJECTED, rejected_at = now(), reason = …
API->>Harbor: (option future) suppression artifact temp
end
API-->>UI: Mise à jour visible pour tous
4. Points d’implémentation marquants
Import obligatoire des images dans K3s
YaraHunter ne scanne que ce qui est présent localement → docker save | sudo k3s ctr images import - manuel obligatoire.
Tentative d’automatisation via sidecar crictl pull → abandonnée (problèmes d’auth et de droits).
Normalisation des flags
Table flags unique :
- type (enum)
- vuln : vulnérabilité détectée (CVE, dépendance, config, etc.)
- malware : détection malware / IOC
- dynamic : résultat d’analyse dynamique (sandbox, comportement)
- policy : violation de règles (compliance, accès, secrets, etc.)
- other : catégorie générique
Valeurs autorisées :
vuln|malware|dynamic|policy|other
- severity (low/medium/high/critical)
Niveau de gravité standardisé :
- 🟢
low - 🟠
medium - 🔴
high - ⚫
critical
- details (JSONB)
Données flexibles et typiées par convention selon type.
Exemples de clés utiles (selon le cas) :
{
"title": "string",
"description": "string",
"source": "string",
"confidence": 0.0,
"timestamp": "2026-02-24T12:34:56Z",
"cve": "CVE-2026-12345",
"cwe": "CWE-79",
"package": "string",
"version": "string",
"fix_version": "string",
"ioc": {
"hash": "string",
"domain": "string",
"ip": "string",
"url": "string"
},
"policy_id": "string",
"rule": "string",
"control": "string",
"evidence": [
{
"type": "log|snippet|stacktrace|file|command|http",
"summary": "string",
"data": "string",
"path": "string",
"line_start": 0,
"line_end": 0,
"timestamp": "2026-02-24T12:34:56Z"
}
],
"recommendation": "string",
"references": ["https://example.com/reference-1", "https://example.com/reference-2"]
}
- raw_path (string)
Chemin optionnel vers l’artefact brut (ex: sur PVC) si besoin :
- logs complets
- dump sandbox
- fichier suspect
- rapport scanner original
Exemple :
/pvc/scans/2026-02-24/job-123/report.json/pvc/artifacts/sample-abc123.bin
Polling Harbor scan
Boucle Tokio avec interval(5s) + compteur max → timeout configurable via variable d’environnement.
Parsing YaraHunter
Patch du binaire pour écrire un JSON structuré dans /output/report.json → monté via PVC (persistent volume claim) → lu par l’API.
Statut des images
Refactor tardif : déplacement du statut depuis table images vers static_analyses pour meilleure normalisation.
5. Limites structurelles & dette technique (état décembre 2024)
- API complètement ouverte (pas d’authentification) → critique si exposée
- Tout le workflow est synchrone dans la requête initiale → pas de file d’attente
- Service d’analyse dynamique jamais terminé → réponses mockées
- Dépendance forte au montage de
~/.kube/config→ impossible multi-cluster sans rework - Aucune notification (email / Slack / webhook) après décision
- Frontend cassé dès qu’on upgrade Harbor au-delà de la v2.9
- Tests : uniquement unitaires sur le domaine (~80 cas) → zéro test d’intégration / E2E
- Pas de monitoring (logs structurés limités, zéro métrique Prometheus)
6. Ce que nous ferions radicalement différemment aujourd’hui
- Ne jamais patcher le frontend Harbor → privilégier webhooks + UI dédiée ou plugin
- Utiliser Kyverno ou OPA Gatekeeper pour des politiques d’admission Kubernetes natives
- Intégrer Cosign + Fulcio + Rekor (Sigstore) pour signature & SLSA attestations dès le départ
- Orchestrer les scans avec Argo Workflows ou Tekton Pipelines (visuel + retry + parallélisme)
- Ajouter Prometheus + Loki / Grafana dès les premiers jours
- Migrer vers axum au lieu d’Actix-web (plus maintenu et plus ergonomique)
- Rendre le malware scanner serverless (Keda / Knative) pour scale-to-zero
- Prévoir un CRD Kubernetes pour les demandes d’admission (operator pattern)
7. Conclusion – Valeur réelle du projet
Le code produit n’est pas industrialisable en l’état : c’est un POC ambitieux avec beaucoup de dette technique.
Mais le projet a été extrêmement formateur :
- Comprendre les limites concrètes de Harbor en profondeur
- Réaliser à quel point l’asynchrone et les files d’attente sont cruciaux dans les workflows de sécurité supply-chain
- Apprendre que le “human approval gate” reste souvent plus puissant et flexible qu’un blocage 100 % automatique
- Découvrir les pièges du patching d’un monolithe open-source (surtout le frontend)
- Toucher à Rust en conditions réalistes, Kubernetes Jobs, reverse-engineering d’API tierces, parsing de rapports non structurés
- Expérimenter la difficulté de coordonner 4 personnes sur un projet distribué avec des deadlines académiques
Si nous devions proposer une suite sérieuse aujourd’hui, nous partirions sur :
- Harbor + webhooks
- Kyverno / Gatekeeper pour admission policies
- Argo Workflows pour orchestration des scans
- UI légère dédiée (ou intégration dans Backstage / Port)
- Sigstore complet pour la provenance
Mais pour un projet de fin de Master 1, nous sommes plutôt fiers du résultat : une démo qui fonctionne, qui impressionne les jurys et qui nous a appris énormément sur la sécurité des conteneurs et l’architecture de systèmes distribués.
Liens (archives non maintenues) :
Questions techniques bienvenues (schéma DB, extraits Rust, patch YaraHunter, etc.).
Bonne lecture et bons projets sécurité !
