Phase 7 — Pipeline DevSecOps
Pipeline CI/CD complet avec validation de sécurité. Chaque étape est bloquante.
Vue d'ensemble
Commit GitLab
│
├── [Test] pytest + lint
├── [SAST] Semgrep + Bandit → DefectDojo
├── [Build] Docker build
├── [SCAN] Trivy → DefectDojo
├── [Push] Harbor (harbor.mounik.ovh)
├── [DAST] OWASP ZAP → DefectDojo
└── [Deploy] ArgoCD sync → k3s
Chaque étape de sécurité est bloquante : si le SAST échoue, le pipeline s'arrête. Aucune image vulnérable n'atteint la production.
Structure du pipeline
# .gitlab-ci.yml (dépôt applicatif, ex: fastapi)
stages:
- test
- sast
- build
- scan
- push
- dast
- deploy
variables:
DOCKER_IMAGE: harbor.mounik.ovh/fastapi/app
DD_URL: https://defectdojo.mounik.ovh
Étape 1 — Test
test:
stage: test
image: python:3.13
script:
- pip install -r requirements.txt
- pytest --cov=. --junitxml=report.xml
- ruff check .
- mypy .
artifacts:
reports:
junit: report.xml
Étape 2 — SAST
sast:
stage: sast
image: python:3.13
script:
# Semgrep
- pip install semgrep
- semgrep --config=auto --output=semgrep-report.json --json .
- |
curl -X POST "${DD_URL}/api/v2/import-scan/" \
-H "Authorization: Token ${DD_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"product_name": "fastapi",
"engagement_name": "Pipeline CI/CD",
"scan_type": "Semgrep JSON Report",
"file": "$(cat semgrep-report.json)"
}'
# Bandit
- pip install bandit
- bandit -r . -f json -o bandit-report.json
- |
curl -X POST "${DD_URL}/api/v2/import-scan/" \
-H "Authorization: Token ${DD_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"product_name": "fastapi",
"scan_type": "Bandit Report",
"file": "$(cat bandit-report.json)"
}'
artifacts:
paths:
- semgrep-report.json
- bandit-report.json
Étape 3 — Build
build:
stage: build
image: docker:27
services:
- docker:27-dind
script:
- docker build -t ${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA} .
- docker tag ${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA} ${DOCKER_IMAGE}:latest
Étape 4 — Scan (Trivy)
scan:
stage: scan
image: docker:27
services:
- docker:27-dind
script:
# Installer Trivy
- apk add --no-cache curl
- curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
# Scanner l'image
- trivy image --format json --output trivy-report.json ${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA}
# Envoyer le rapport à DefectDojo
- |
curl -X POST "${DD_URL}/api/v2/import-scan/" \
-H "Authorization: Token ${DD_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"product_name": "fastapi",
"scan_type": "Trivy Scan",
"file": "$(cat trivy-report.json)"
}'
# Bloquer si vulnérabilités critiques
- trivy image --severity CRITICAL --exit-code 1 ${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA}
artifacts:
paths:
- trivy-report.json
Étape 5 — Push
push:
stage: push
image: docker:27
services:
- docker:27-dind
script:
- docker login harbor.mounik.ovh -u admin -p ${HARBOR_PASSWORD}
- docker push ${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA}
- docker push ${DOCKER_IMAGE}:latest
Étape 6 — DAST
dast:
stage: dast
image: ghcr.io/zaproxy/zaproxy:stable
script:
# Lancer ZAP en mode headless
- zap-full-scan.py -t https://fastapi.mounik.ovh \
-r zap-report.html \
-w zap-report.json
# Envoyer à DefectDojo
- |
curl -X POST "${DD_URL}/api/v2/import-scan/" \
-H "Authorization: Token ${DD_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"product_name": "fastapi",
"scan_type": "OWASP ZAP Report",
"file": "$(cat zap-report.json)"
}'
artifacts:
paths:
- zap-report.html
- zap-report.json
Étape 7 — Déploiement
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache curl git
- curl -LO https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
- chmod +x argocd-linux-amd64 && mv argocd-linux-amd64 /usr/local/bin/argocd
script:
# Cloner le dépôt d'infrastructure (contient les manifests)
- git clone https://gitlab.com/Mounicou/homelab-proxmox.git infra
- cd infra/manifests/fastapi
# Mettre à jour l'image dans le manifest
- sed -i "s|image:.*|image: ${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA}|" deployment.yaml
# Commit et push du nouveau manifest
- git config user.email "pipeline@mounik.ovh"
- git config user.name "GitLab CI Pipeline"
- git add deployment.yaml
- git commit -m "chore: update fastapi image to ${CI_COMMIT_SHORT_SHA}" || true
- git push "https://oauth2:${GITLAB_TOKEN}@gitlab.com/Mounicou/homelab-proxmox.git" HEAD:main
# Sync ArgoCD
- argocd login argocd.mounik.ovh --grpc-web --username admin --password ${ARGOCD_PASSWORD}
- argocd app sync fastapi --grpc-web
Pipeline complet
# .gitlab-ci.yml (structure complète)
stages:
- test
- sast
- build
- scan
- push
- dast
- deploy
variables:
DOCKER_IMAGE: harbor.mounik.ovh/fastapi/app
DD_URL: https://defectdojo.mounik.ovh
# Règles : exécuter sur toutes les branches, déployer uniquement sur main
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
include:
- local: /.gitlab/ci/test.yml
- local: /.gitlab/ci/sast.yml
- local: /.gitlab/ci/build.yml
- local: /.gitlab/ci/scan.yml
- local: /.gitlab/ci/push.yml
- local: /.gitlab/ci/dast.yml
- local: /.gitlab/ci/deploy.yml
Variables CI/CD requises
| Variable | Source | Description |
|---|---|---|
HARBOR_PASSWORD |
Vault (homelab/harbor) |
Mot de passe admin Harbor |
DD_API_TOKEN |
Interface DefectDojo | Token API pour push des rapports |
DD_URL |
Défini dans GitLab | URL de DefectDojo |
GITLAB_TOKEN |
GitLab.com | Token pour push des manifests sur homelab-proxmox |
ARGOCD_PASSWORD |
Vault (homelab/argocd) |
Mot de passe admin ArgoCD |
VAULT_ADDR |
Défini dans GitLab | Adresse du serveur Vault |
VAULT_TOKEN |
Vault | Token d'accès aux secrets |
Vérification
# Lancer un pipeline manuellement depuis GitLab.com
# Vérifier chaque étape dans l'interface CI/CD
# Vérifier les rapports dans DefectDojo
curl -X GET https://defectdojo.mounik.ovh/api/v2/findings/ \
-H "Authorization: Token ${DD_API_TOKEN}"
Pour aller plus loin
- Phase 4.7 — DefectDojo — configuration de l'instance
- Phase 6 — Applications Python — applications à sécuriser