🖨️ Version PDF
Voici un TP DevSecOps complet qui combine SAST, DAST, Dependency Scanning et Secret Detection dans GitLab CI/CD — avec un maximum d’explications pas à pas.
Objectif : Intégrer la sécurité à chaque étape du pipeline CI/CD et apprendre à lire/corriger les rapports.
gitlab-runner register --executor docker
alpine:latest
Note licensing : Sur GitLab Ultimate, les templates Security sont natifs. Si certaines fonctionnalités sont limitées, vous trouverez en fin de TP une alternative “outils open source” (Gitleaks, Trivy, Bandit, etc.).
app/index.js
const express = require('express'); const app = express(); // Exemple volontairement simple // on améliorera plus tard les en-têtes de sécurité et la validation des entrées app.get('/', (req, res) => res.send('Salut depuis GitLab Security !')); app.listen(8080, () => console.log('Serveur démarré sur le port 8080'));
app/package.json
{ "name": "securite-demo", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "start": "node index.js", "test": "echo \"(placeholder)\" && exit 0" }, "dependencies": { "express": "^4.19.2" } }
app/Dockerfile
FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . EXPOSE 8080 CMD ["npm", "start"]
Si on reprend la métaphore de la constrution d’une maison :
But : avoir un pipeline clair, segmenté par stages.
Vous allez créer un fichier .gitlab-ci.yml à la racine de votre projet :
.gitlab-ci.yml
# ------------------- # 1) Définition des stages # ------------------- stages: - build - test - sast - secrets - deps - package - dast # ---------------------- # 2) Variables utiles # ---------------------- variables: # Utilisées si vous faites tourner l'app localement dans un job précédent # et que vous exposez le service sur le même runner (option "services") DAST_WEBSITE: "http://app:8080" DAST_FULL_SCAN_ENABLED: "true" # ---------------------- # 3) Build de l'image app (Docker) # ----------------------- build-image: stage: build image: docker:24.0.5 services: - docker:24.0.5-dind variables: DOCKER_DRIVER: overlay2 script: - echo "Build de l'image de l'application" - docker build -t "$CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHORT_SHA" app rules: - if: $CI_COMMIT_BRANCH tags: [docker] artifacts: when: on_success paths: - app/ expire_in: 1 day # ------------------------------------------ # 4) Tests applicatifs (placeholder ici) # ------------------------------------------ unit-tests: stage: test image: node:18-alpine script: - cd app && npm ci - npm test tags: [docker] # --------------------------- # 5) SAST — Static Application Security Testing # Inclut les scanners GitLab en fonction du langage # --------------------------- include: - template: Security/SAST.gitlab-ci.yml sast: stage: sast needs: [] # SAST n'a pas besoin que l'app tourne tags: [docker] # --------------------------- # 6) Secret Detection — secrets dans l'historique git # --------------------------- include: - template: Security/Secret-Detection.gitlab-ci.yml secret_detection: stage: secrets needs: [] tags: [docker] # --------------------------- # 7) Dependency Scanning — CVE dans les libs & lockfiles # --------------------------- include: - template: Security/Dependency-Scanning.gitlab-ci.yml dependency_scanning: stage: deps needs: [] tags: [docker] # --------------------------- # 8) DAST — Dynamic Application Security Testing # Option A: scanner cible *locale* via "services" (app conteneurisée) # Option B: scanner une URL *déjà déployée* (staging) # --------------------------- # --- Option A : lancer l'app comme service pour DAST --- dast: stage: dast image: docker:24.0.5 services: - name: docker:24.0.5-dind - name: $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHORT_SHA alias: app variables: DOCKER_DRIVER: overlay2 # DAST_WEBSITE défini plus haut → "http://app:8080" DAST_FULL_SCAN_ENABLED: "true" script: - echo "Lancement du scan DAST contre ${DAST_WEBSITE}" # Le template DAST s'exécute via 'dast' quand inclus, # ici, on fait un echo pour lisibilité du job. tags: [docker] needs: - job: build-image artifacts: reports: dast: gl-dast-report.json expire_in: 1 week # On injecte le template DAST au niveau global, mais selon GitLab, # il peut créer son propre job 'dast' ; si doublon, renommer ce job 'dast:active' # --- Option B : (alternative) scanner une URL déjà déployée --- # dast-staging: # stage: dast # variables: # DAST_WEBSITE: "https://votre-staging.exemple.com" # DAST_FULL_SCAN_ENABLED: "true" # tags: [docker] # rules: # - if: '$CI_COMMIT_BRANCH == "main"' # artifacts: # reports: # dast: gl-dast-report.json # expire_in: 1 week
les stages ordonne le pipeline (build puis test puis analyses puis package puis dast).
stages
l’include : template: Security/... active les scanners GitLab pour SAST, Secrets et les dépendances.
include : template: Security/...
Avec DAST Option A (TP local) : on lance l’app comme “service” Docker dans le job DAST, accessible via http://app:8080.
Avec DAST Option B (prod/stage) : on scanne directement une URL déployée.
artifacts > reports > dast produit un rapport DAST lisible dans l’onglet Security et dans les MR widgets.
artifacts > reports > dast
À vous de jouer : commencez par l’option A pour le TP local, puis passez à l’option B quand vous avez une URL de staging.
Critères de réussite :
Objectif : apprendre à lire/prioriser les vulnérabilités.
Exemple d’ajout d’en-têtes de sécurité (Node/Express) :
// app/index.js const helmet = require('helmet'); app.use(helmet());
Pensez à l’installer : npm i helmet
npm i helmet
Objectif : capacité à expliquer la vulnérabilité et justifier la correction.
Nous pourrions aller plus loin, mais ce serait trop long…
Auteur : Philippe Bouget