Exercice 05 : Capstone - Pipeline DevSecOps complet
🎯 Objectifs
À la fin de cet exercice, vous serez capable de :
- ✅ Créer un pipeline CI/CD avec plusieurs quality gates de sécurité
- ✅ Combiner CodeQL, Trivy et audit de dépendances dans un workflow unique
- ✅ Implémenter une logique de blocage conditionnelle selon la sévérité
- ✅ Générer un rapport de sécurité agrégé publié comme artefact de build
- ✅ Appliquer la règle : "Shift Left" - sécurité au plus tôt dans le pipeline
Durée estimée : 1h
Difficulté : ⭐⭐⭐☆☆ (Avancé)
Prérequis : Exercices 01 à 04 complétés
📖 Contexte
Vous êtes responsable DevSecOps dans une équipe. On vous demande de construire un pipeline "secure by default" pour tous les nouveaux projets. Ce pipeline doit exécuter automatiquement toutes les vérifications de sécurité et n'autoriser le déploiement que si l'ensemble des quality gates passe.
Le principe "Shift Left" : plus une vulnérabilité est détectée tôt (shift left = plus tôt dans le pipeline), moins elle coûte à corriger.
📋 Énoncé
Construisez un pipeline DevSecOps complet avec Dependabot, CodeQL, Trivy et reporting.
🧭 Déroulement de l'exercice
Tâche 1 : Structure du projet
mkdir devsecops-capstone && cd devsecops-capstone
git init
# Application
cat > package.json << 'EOF'
{
"name": "devsecops-app",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.0",
"helmet": "^7.1.0"
},
"devDependencies": {
"jest": "^29.0.0"
}
}
EOF
cat > app.js << 'EOF'
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet()); // Headers de sécurité automatiques
app.use(express.json());
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.get('/api/user/:id', (req, res) => {
const userId = req.params.id;
// Validation de l'ID
if (!/^\d+$/.test(userId)) {
return res.status(400).json({ error: 'ID invalide' });
}
res.json({ id: userId, name: 'Exemple User' });
});
module.exports = app;
if (require.main === module) {
app.listen(3000, () => console.log('Server started'));
}
EOF
cat > Dockerfile << 'EOF'
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY app.js .
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
HEALTHCHECK --interval=30s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "app.js"]
EOFTâche 2 : Configurer Dependabot
mkdir -p .github
cat > .github/dependabot.yml << 'EOF'
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
groups:
patches:
update-types: ["patch"]
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
EOFTâche 3 : Pipeline DevSecOps principal
mkdir -p .github/workflows
cat > .github/workflows/devsecops-pipeline.yml << 'EOF'
name: DevSecOps Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
# Permissions minimales (principe du moindre privilège)
permissions:
contents: read
security-events: write
pull-requests: read
env:
IMAGE_NAME: devsecops-app
jobs:
# ═══ COUCHE 1 : Audit des dépendances ═══
dependency-audit:
name: Audit des dépendances npm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Installer les dépendances
run: npm ci
- name: Audit npm (bloquant sur CRITICAL/HIGH)
run: |
npm audit --audit-level=high --json > npm-audit.json || true
CRITICAL=$(cat npm-audit.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('metadata',{}).get('vulnerabilities',{}).get('critical',0))")
HIGH=$(cat npm-audit.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('metadata',{}).get('vulnerabilities',{}).get('high',0))")
echo "CRITICAL: $CRITICAL, HIGH: $HIGH"
if [ "$CRITICAL" -gt "0" ]; then
echo "❌ $CRITICAL vulnérabilités CRITICAL détectées"
exit 1
fi
- name: Sauvegarder le rapport
uses: actions/upload-artifact@v4
if: always()
with:
name: npm-audit-report
path: npm-audit.json
# ═══ COUCHE 2 : Analyse statique CodeQL ═══
code-analysis:
name: Analyse statique CodeQL
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: javascript
queries: security-and-quality
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3
with:
category: "/language:javascript"
output: codeql-results.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: codeql-results.sarif
# ═══ COUCHE 3 : Scanner l'image Docker ═══
container-security:
name: Sécurité du conteneur Docker
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build de l'image
run: docker build -t $IMAGE_NAME:${{ github.sha }} .
- name: Scanner l'image (bloquant sur CRITICAL)
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ env.IMAGE_NAME }}:${{ github.sha }}'
format: 'table'
exit-code: '1'
severity: 'CRITICAL'
ignore-unfixed: true
- name: Rapport SARIF Trivy
uses: aquasecurity/trivy-action@master
if: always()
with:
image-ref: '${{ env.IMAGE_NAME }}:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
category: 'trivy'
# ═══ RAPPORT FINAL ═══
security-report:
name: Rapport de sécurité
runs-on: ubuntu-latest
needs: [dependency-audit, code-analysis, container-security]
if: always()
steps:
- name: Récupérer les artefacts
uses: actions/download-artifact@v4
with:
name: npm-audit-report
path: reports/
- name: Générer le rapport consolidé
run: |
STATUS_DEPS="${{ needs.dependency-audit.result }}"
STATUS_CODE="${{ needs.code-analysis.result }}"
STATUS_CONTAINER="${{ needs.container-security.result }}"
cat > reports/security-summary.md << REPORT
# Rapport de Sécurité - $(date -u +"%Y-%m-%d %H:%M UTC")
## Résumé des quality gates
| Vérification | Statut |
|---|---|
| Audit dépendances npm | $([ "$STATUS_DEPS" = "success" ] && echo "✅ Passé" || echo "❌ Échoué") |
| Analyse CodeQL | $([ "$STATUS_CODE" = "success" ] && echo "✅ Passé" || echo "❌ Échoué") |
| Scanner Docker | $([ "$STATUS_CONTAINER" = "success" ] && echo "✅ Passé" || echo "❌ Échoué") |
## Commit
- SHA: ${{ github.sha }}
- Branch: ${{ github.ref_name }}
- Auteur: ${{ github.actor }}
REPORT
cat reports/security-summary.md
- name: Publier le rapport
uses: actions/upload-artifact@v4
with:
name: security-report-${{ github.sha }}
path: reports/
retention-days: 30
# ═══ DÉPLOIEMENT (uniquement si tout passe) ═══
deploy:
name: Déployer (si tout passe)
runs-on: ubuntu-latest
environment: staging
needs: [dependency-audit, code-analysis, container-security]
if: |
github.ref == 'refs/heads/main' &&
needs.dependency-audit.result == 'success' &&
needs.code-analysis.result == 'success' &&
needs.container-security.result == 'success'
steps:
- name: Déploiement
run: echo "✅ Tous les quality gates passés - Déploiement en staging"
EOFTâche 4 : Pousser et observer
git add .
git commit -m "feat: pipeline DevSecOps complet avec 3 quality gates"
git push origin mainObservez dans l'onglet Actions :
- Les 4 jobs tournent en parallèle (les 3 scans + le rapport)
- Le job
deployne tourne que si les 3 scans sont verts - Dans Security → Code scanning, les résultats CodeQL et Trivy apparaissent
Tâche 5 : Tester le blocage
Introduisez une vulnérabilité pour vérifier que le pipeline bloque :
# Downgrader express vers une version avec CVE
npm install express@4.17.1
git add package.json package-lock.json
git commit -m "test: introduire une vulnérabilité pour tester le blocage"
git pushVérification : Le job dependency-audit doit échouer si des CRITICAL sont trouvées, ce qui empêche le job deploy de se lancer.
✅ Vérification du résultat
.github/dependabot.ymlcouvre npm, docker et github-actions- Le workflow contient les 3 couches de sécurité (deps, code, container)
- Le job
deployne se lance que si les 3 quality gates passent - Le rapport de sécurité est publié comme artefact de build
- Le pipeline se visualise correctement dans l'onglet Actions
💡 À retenir
Un pipeline DevSecOps en 3 couches :
Code → npm audit → CodeQL → Trivy Image
↓ (si tout passe)
Déploiement
↓
Rapport de sécurité archivé 30 joursShift Left = détection précoce = correction moins coûteuse : une CVE détectée en développement coûte 6x moins cher à corriger qu'une CVE détectée en production.
✨ Solution Complète
Le workflow complet est défini dans .github/workflows/devsecops-pipeline.yml ci-dessus. La clé est le needs conditionnel sur le job deploy qui garantit que les quality gates sont tous verts avant tout déploiement.