Indian Food : pipeline DevSecOps pour un site vitrine
Site vitrine statique d'un restaurant, déployé sur Oracle Cloud ARM64 derrière une chaîne DevSecOps : analyse statique, scan de dépendances, tests dynamiques et déploiement automatisé.
- L'objectif
- Montrer qu'une chaîne DevSecOps complète s'applique même au plus petit projet : un simple site vitrine.
- Ma démarche
- Pipeline GitHub Actions : Gitleaks et Trivy bloquants, SAST Semgrep, build ARM64, déploiement, puis scan dynamique OWASP ZAP.
- Le résultat
- Site déployé derrière des portes de sécurité, en-têtes HTTP corrigés, analyse de risques STRIDE.
Contexte
C'est un projet pédagogique. Le but était de montrer que les pratiques DevSecOps s'appliquent aussi à une petite application. J'ai pris le cas le plus simple possible, un site vitrine statique pour un restaurant de cuisine indienne, et je lui ai appliqué une chaîne de sécurité du commit jusqu'au déploiement en production.
Le contenu est volontairement trivial. Ce qui est mis en avant, c'est l'infrastructure et le pipeline.

Objectifs
- Déployer un site en production avec HTTPS sur un serveur personnel (Oracle Cloud Free Tier)
- Implémenter une chaîne CI/CD avec des security gates bloquants
- Couvrir les quatre vecteurs d'analyse : secrets, code statique, dépendances, tests dynamiques
- Comprendre le Cyber Resilience Act (CRA) en pratique, pas seulement lire le texte, mais traduire ses exigences en livrables concrets sur un vrai projet déployé
Stack technique
| Couche | Choix | Raison |
|---|---|---|
| Site | HTML / CSS / Vanilla JS | Zéro dépendance, surface d'attaque minimale |
| Conteneur | nginx:alpine | Image légère (~7 Mo), durcissement possible |
| Orchestration | Docker Compose | Simple, suffisant pour un seul service |
| Registry | GitHub Container Registry (GHCR) | Intégration native avec GitHub Actions, gratuit |
| CI/CD | GitHub Actions | Écosystème riche en actions de sécurité |
| Infra | Oracle Cloud ARM64 (Ampere A1) | Free Tier permanent, architecture ARM64 |
| DNS | Sous-domaine sinoro.fr | Enregistrement A statique vers le VPS |
| SSL | Let's Encrypt (via Traefik) | Certificat automatisé, renouvellement géré par le proxy |
| Reverse proxy | Traefik v2.11 | Terminaison TLS + routing par labels Docker |
Architecture de déploiement
Pipeline DevSecOps détaillé
1. Gitleaks, Détection de secrets
Scan de l'ensemble du dépôt à la recherche de credentials oubliés (tokens, clés SSH, mots de passe). Le déploiement est bloqué si un secret est détecté.
2. Semgrep, SAST (Static Application Security Testing)
Analyse statique du code source (public/) pour détecter des patterns dangereux : injections, mauvaise gestion des entrées, fonctions obsolètes. Les règles auto couvrent HTML, CSS et JavaScript. Semgrep est lancé sans --error : il produit un rapport mais ne bloque pas le déploiement, contrairement à Gitleaks et Trivy.
3. Trivy, SCA
Scan du système de fichiers pour identifier les CVE connues sur les composants utilisés. Le job échoue sur toute CVE critique corrigeable (severity: CRITICAL, exit-code: 1, ignore-unfixed).
En complément, un Software Bill of Materials (SBOM) au format CycloneDX 1.6 a été généré avec Trivy, en dehors du pipeline, ainsi qu'un rapport SCA. C'est l'inventaire des composants logiciels que le CRA exige pour la traçabilité.
4. Build ARM64
L'image Docker est compilée pour linux/arm64 via QEMU + Docker Buildx, adapté à l'architecture Ampere d'Oracle Cloud. Le build multi-plateforme sur runner ubuntu-latest (x86_64) est transparent grâce à l'émulation.
5. Déploiement via runner auto-hébergé
Le job de déploiement s'exécute sur un runner GitHub Actions auto-hébergé, installé directement sur le VPS : il tire la nouvelle image depuis GHCR et relance le container avec docker compose up -d --remove-orphans, puis nettoie les anciennes images. Aucune clé SSH n'est stockée dans GitHub, et seule l'image Docker circule.
6. OWASP ZAP, DAST (Dynamic Application Security Testing)
Scan dynamique du site en production après déploiement. ZAP simule un attaquant en testant les en-têtes HTTP, les redirections, les éléments HTML contrôlables, les politiques de cache, etc. Le rapport HTML est archivé comme artifact du workflow.
Ce que ZAP a identifié, et corrigé
C'est ici que le DAST a eu le plus de valeur concrète. La première exécution de ZAP a remonté une série de WARN sur des en-têtes de sécurité absents ou mal configurés. Chacun a été corrigé directement dans la configuration Nginx (docker/nginx.conf), puis re-vérifié au scan suivant.
| Vulnérabilité (ZAP) | Correction | Risque si non corrigé |
|---|---|---|
| HSTS absent [10035] | Strict-Transport-Security: max-age=31536000 | SSL stripping : un attaquant en MITM force la connexion en HTTP clair et intercepte/modifie le trafic |
| CSP absente [10038] | Content-Security-Policy strict ajouté | Aucune barrière contre l'injection de scripts/contenu : un XSS s'exécute librement dans le navigateur du visiteur |
CSP style-src 'unsafe-inline' [10055] | Styles inline externalisés en classe CSS, 'unsafe-inline' retiré | Un CSP avec unsafe-inline n'arrête pas l'injection de styles malveillants (défacement, exfiltration de données via CSS) |
| CSP directives sans fallback [10055] | Ajout de frame-ancestors, form-action, base-uri, object-src | Clickjacking (page embarquée dans une iframe pirate), détournement de soumission de formulaire, injection d'objets |
| COOP / COEP / CORP manquants [90004] | Cross-Origin-Opener-Policy, -Embedder-Policy, -Resource-Policy | Attaques cross-origin (fenêtres détournées, fuites mémoire type Spectre) |
| Permissions-Policy absente [10063] | Header désactivant géoloc/micro/caméra/paiement | Une page tierce embarquant le site pourrait solliciter des API sensibles du navigateur |
| Fuite de version serveur [10036] / banner [10009] | server_tokens off | La version exacte de Nginx exposée facilite le ciblage d'exploits CVE connus |
Le tri des faux positifs
Tout aussi formateur : ZAP a aussi remonté des alertes qui n'étaient pas des vulnérabilités dans ce contexte. Les écarter à la légère est une erreur ; les justifier l'est tout autant. J'ai documenté chaque exclusion dans .zap/rules.tsv :
- Absence of Anti-CSRF Tokens [10202], le site est 100 % statique, aucun formulaire POST : pas de surface CSRF.
- Storable and Cacheable Content [10049], la mise en cache est souhaitable pour un site statique, pas un défaut.
- Modern Web Application [10109], alerte purement informative, aucune action requise.
Analyse de sécurité (méthode STRIDE)
| Risque | Score initial | Mesures | Résiduel |
|---|---|---|---|
| Interception HTTP | 4 | HTTPS TLS 1.3 + HSTS | Faible ✅ |
| Accès SSH non autorisé | 12 | Clé ED25519 + Fail2ban + UFW | Faible ✅ |
| Défacement du site | 6 | Container éphémère + Trivy | Faible ✅ |
| Secrets dans le code | 8 | Gitleaks + GitHub Secrets | Faible ✅ |
| Déni de service | 6 | Fail2ban + cgroups Docker | Moyen ⚠️ |
| CVE dépendances | 6 | Trivy + Security Gate | Faible ✅ |
| Attaques navigateur | 4 | Headers HTTP + OWASP ZAP | Faible ✅ |
Le seul risque résiduel accepté est le déni de service : une protection complète nécessiterait un WAF ou un CDN (Cloudflare), hors scope pour ce projet.
Limites
- Pas de WAF : le site est directement exposé sans filtrage applicatif avancé.
- Nœud unique : pas de haute disponibilité. Si le VPS tombe, le site est indisponible.
- DAST détectif, pas préventif : ZAP scanne après déploiement. Une vulnérabilité n'est donc visible qu'une fois le site déjà en ligne, un WAF en frontal comblerait ce délai.
- Site statique pur : aucune interaction serveur, donc le périmètre de test de sécurité reste limité.
Évolutions envisagées
- Cloudflare en frontal : proxy gratuit qui apporte DDoS protection, CDN et WAF basique, résoudrait le risque DoS.
- Wazuh (SIEM) : monitorer les logs Nginx pour détecter en temps réel les tentatives d'attaque (SQLi, XSS, scans) et déclencher des alertes, complète le DAST par de la détection en continu.
- Watchtower : mise à jour automatique de l'image Docker dès qu'une nouvelle version est publiée sur GHCR.
- Notifications Slack/Discord : alertes en cas d'échec d'un security gate en CI.
- Tests de charge (k6 ou Locust) : ajouter un job de performance au pipeline pour détecter les régressions.
Le CRA en pratique
Le Cyber Resilience Act est un règlement européen qui impose des exigences de cybersécurité aux produits numériques mis sur le marché. Première précision importante : ce site ne tombe pas dans le champ d'application du CRA. Le règlement cible les produits avec des éléments numériques (PDE) commercialisés, un site vitrine personnel n'est pas mis sur le marché au sens du règlement.
L'objectif n'était donc pas de "se conformer au CRA", mais de s'en servir comme cadre méthodologique pour apprendre à structurer une démarche de sécurité : qu'est-ce qu'une évaluation des risques formelle ? Comment documenter un SBOM ? Que met-on dans une déclaration de conformité ?
| Exigence CRA | Traduction concrète dans ce projet |
|---|---|
| Évaluation des risques | Analyse STRIDE sur 7 vecteurs, score et risque résiduel documentés |
| Gestion des vulnérabilités | Trivy SCA + Security Gate bloquant en CI |
| Traçabilité des composants | SBOM CycloneDX 1.6 généré avec Trivy (hors pipeline) |
| Tests de sécurité | SAST (Semgrep, non bloquant) + DAST (OWASP ZAP post-déploiement) dans le pipeline |
| Sécurité par conception | Site 100% statique, container sans privilèges, surface d'attaque minimale |
| Documentation technique | Architecture, analyse des risques, déclaration de conformité |
L'exercice a mis en évidence un écart entre la théorie et la pratique : le CRA parle d'"évaluation des risques" et de "mesures appropriées" de façon abstraite. Appliquer ça sur un projet réel, même hors scope réglementaire, force à prendre des décisions tranchées : quel risque on accepte, pourquoi, et comment on le documente. Le risque DoS accepté faute de WAF en est un exemple direct.
Ce que ce projet m'a appris
Appliquer DevSecOps à un site statique est intentionnellement disproportionné, et c'est le point. L'objectif était de maîtriser la mécanique du pipeline (ordering des jobs, needs, permissions, artifacts) et les outils (Gitleaks, Semgrep, Trivy, ZAP) dans un contexte où une erreur ne coûte rien.
Le vrai enseignement est venu d'un incident réel : un token GitHub commité par erreur, détecté par Gitleaks, qui a stoppé le pipeline avant la construction et la publication de l'image. Le pipeline a fonctionné exactement comme prévu, c'est plus formateur que n'importe quel cours.
Sur le CRA : comprendre un règlement en lisant ses articles est trompeur. C'est en cherchant à générer un SBOM conforme ou à documenter un risque résiduel accepté, même sur un projet hors scope, qu'on mesure ce que le texte exige vraiment des fabricants qui, eux, y sont soumis.
Projets proches
Mon budget : une application de prévision de solde
Application web qui répond à "combien j'aurai à telle date ?" et "quand est-ce que ça coince ?", avec un moteur de calcul testé, plusieurs budgets étanches et une authentification à double vérification. Déployée sur mon VPS ARM64.
2026Next.jsTypeScriptSQLiteargon2id
Umami : statistiques de visite auto-hébergées
Statistiques de visite sans cookie, auto-hébergées sur VPS ARM64. La collecte est publique, le dashboard n'est accessible que par le VPN WireGuard.
2026UmamiDockerTraefikWireGuard
Wazuh : un SIEM/XDR sur mon VPS
Wazuh installé sur mon VPS ARM64 pour voir comment un SIEM/XDR fonctionne sur une vraie machine : collecte de logs, détection d'intrusion et premier audit de conformité CIS.
2026WazuhSIEMXDRSCA