DevSecOps : intégrer la sécurité dans votre pipeline CI/CD Java
DevSecOps en pratique : SAST, DAST, analyse des dépendances, secrets scanning et security gates dans votre CI/CD Java avec GitHub Actions. Exemples concrets.
En 2026, la cybersécurité est devenue la compétence IT la plus demandée en recrutement. Les attaques par la supply chain (dépendances compromises), les secrets exposés dans les repos et les injections dans les APIs sont responsables de la majorité des incidents. Le principe du Security by Design — intégrer la sécurité dès le début du cycle de développement — est passé de bonne pratique à exigence réglementaire dans de nombreux secteurs.
Voici comment construire un pipeline DevSecOps complet pour une application Java.
Les 5 piliers du DevSecOps
Code → Build → Test → Release → Deploy → Operate
↑ ↑ ↑ ↑ ↑ ↑
Secrets SAST DAST Deps Container Runtime
scan scan scan scan monitoring
Chaque étape a son outil de sécurité associé. L’objectif : détecter au plus tôt, là où le coût de correction est le plus faible.
1. Secrets scanning : ne jamais committer de credentials
La première règle : aucun secret (clé API, mot de passe, token) ne doit entrer dans le repository.
Gitleaks en pre-commit (bloque avant le push) :
# Installation
brew install gitleaks
# Configuration .gitleaks.toml
cat > .gitleaks.toml << 'EOF'
[extend]
useDefault = true
[[rules]]
id = "custom-internal-api-key"
description = "Clé API interne"
regex = '''MYAPP_[A-Z0-9]{32}'''
tags = ["key", "custom"]
[allowlist]
description = "Fichiers exemptés"
paths = [".gitleaks.toml", "tests/fixtures/"]
EOF
# En hook pre-commit
echo 'gitleaks protect --staged --config=.gitleaks.toml' > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
Dans le CI (GitHub Actions) :
# .github/workflows/security.yml
name: Security
on: [push, pull_request]
jobs:
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # historique complet pour Gitleaks
- name: Scan des secrets
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
2. SAST : analyser le code source
Semgrep est le SAST open source le plus utilisé. Il dispose de règles prêtes à l’emploi pour Java (Spring Boot, injection SQL, désérialisation).
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep SAST
uses: semgrep/semgrep-action@v1
with:
config: >-
p/java
p/spring
p/owasp-top-ten
p/sql-injection
generateSarif: "1"
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: semgrep.sarif
Exemple de vulnérabilité que Semgrep détecte :
// ❌ Injection SQL — Semgrep rule: java.lang.security.sqli.jdbc-sqli
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name) {
String query = "SELECT * FROM users WHERE name = '" + name + "'";
return jdbcTemplate.query(query, userRowMapper);
// Un attaquant peut passer name = "'; DROP TABLE users; --"
}
// ✅ Version sécurisée — paramètre lié, pas de concaténation
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name) {
return jdbcTemplate.query(
"SELECT * FROM users WHERE name = ?",
userRowMapper,
name
);
}
CodeQL (GitHub native, plus puissant) :
codeql:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- name: Init CodeQL
uses: github/codeql-action/init@v3
with:
languages: java
queries: security-extended
- name: Build (CodeQL doit voir la compilation)
run: mvn compile -DskipTests
- name: Analyze
uses: github/codeql-action/analyze@v3
3. Analyse des dépendances
Les librairies tierces sont la principale surface d’attaque (Log4Shell, Spring4Shell, etc.).
OWASP Dependency-Check dans Maven :
<!-- pom.xml -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>9.2.0</version>
<configuration>
<failBuildOnCVSS>7</failBuildOnCVSS> <!-- Échec si CVSS ≥ 7 (HIGH) -->
<suppressionFile>dependency-check-suppressions.xml</suppressionFile>
<formats>
<format>HTML</format>
<format>SARIF</format>
</formats>
</configuration>
</plugin>
dependency-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: OWASP Dependency Check
run: mvn dependency-check:check
env:
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
- name: Upload rapport HTML
uses: actions/upload-artifact@v4
if: always()
with:
name: dependency-check-report
path: target/dependency-check-report.html
Trivy pour scanner l’image Docker avant de la pousser :
container-scan:
runs-on: ubuntu-latest
needs: build
steps:
- name: Build image
run: docker build -t mon-app:${{ github.sha }} .
- name: Scan Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: mon-app:${{ github.sha }}
format: sarif
output: trivy.sarif
severity: HIGH,CRITICAL
exit-code: '1' # fail si HIGH ou CRITICAL
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy.sarif
Dockerfile sécurisé (ce que Trivy va vérifier) :
# ❌ Image trop large, user root, pas de multi-stage
FROM openjdk:21
COPY target/app.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
# ✅ Multi-stage, distroless, non-root, pas de secrets
FROM maven:3.9-eclipse-temurin-21-alpine AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -q
COPY src/ src/
RUN mvn package -DskipTests -q
FROM gcr.io/distroless/java21-debian12:nonroot
COPY --from=builder /build/target/app.jar /app/app.jar
EXPOSE 8080
USER nonroot
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
4. DAST : tester l’application en cours d’exécution
OWASP ZAP attaque votre API comme le ferait un pentesteur.
dast:
runs-on: ubuntu-latest
needs: deploy-staging
steps:
- name: Start application
run: |
docker run -d --name app -p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=test \
mon-app:${{ github.sha }}
sleep 15
- name: ZAP API Scan
uses: zaproxy/action-api-scan@v0.7.0
with:
target: http://localhost:8080/v3/api-docs # Endpoint OpenAPI
rules_file_name: zap-rules.tsv
fail_action: true
cmd_options: '-a'
- name: Upload rapport ZAP
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-report
path: report_html.html
Règles ZAP à personnaliser (zap-rules.tsv) :
# Format: id action description
10016 IGNORE Web Browser XSS Protection Not Enabled (header géré par le CDN)
10017 IGNORE Cross-Domain JavaScript Source File Inclusion (faux positif CDN)
10038 WARN Content Security Policy (CSP) Header Not Set
5. Security gates et quality gates
Centralisez les seuils de qualité sécurité dans un fichier de config :
# .github/security-policy.yml
sast:
max_critical: 0
max_high: 2
block_on_new: true
dependencies:
max_cvss: 7.0 # block si CVSS ≥ 7
max_critical_cves: 0
containers:
max_critical: 0
max_high: 5
secrets:
block_on_any: true # zéro tolérance
Job qui lit cette politique et décide si le pipeline peut continuer :
security-gate:
runs-on: ubuntu-latest
needs: [secrets-scan, sast, dependency-check, container-scan]
if: always()
steps:
- name: Évaluer les résultats
run: |
FAILED=0
if [ "${{ needs.secrets-scan.result }}" != "success" ]; then
echo "❌ Secrets scan échoué"
FAILED=1
fi
if [ "${{ needs.sast.result }}" != "success" ]; then
echo "❌ SAST échoué"
FAILED=1
fi
if [ "${{ needs.dependency-check.result }}" != "success" ]; then
echo "❌ Dependency check échoué"
FAILED=1
fi
if [ $FAILED -eq 1 ]; then
echo "Pipeline bloqué par les security gates."
exit 1
fi
echo "✅ Tous les security gates passés."
Bonnes pratiques Spring Boot
// ✅ Désactiver les actuator endpoints sensibles en production
// application-production.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics
exclude: env,beans,mappings,threaddump,heapdump
// ✅ Headers de sécurité avec Spring Security
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; script-src 'self'"))
.frameOptions(HeadersConfigurer.FrameOptionsConfig::deny)
.httpStrictTransportSecurity(hsts -> hsts
.maxAgeInSeconds(31536000)
.includeSubDomains(true))
)
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
Pipeline complet GitHub Actions
# .github/workflows/security-full.yml
name: Security Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
secrets-scan: { ... } # < 2 min — sur chaque PR
sast: { ... } # < 5 min — sur chaque PR
dependency-check:{ ... } # < 3 min — sur chaque PR
container-scan: { ... } # < 5 min — sur main seulement
dast: { ... } # < 15 min — sur main seulement
security-gate: { ... } # bloque ou valide
deploy:
needs: security-gate
if: needs.security-gate.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: ./deploy.sh production
DevSecOps n’est pas un projet en soi : c’est une succession de petits ajouts au pipeline existant. Commencez par le secrets scanning (zéro faux positif, valeur immédiate), puis ajoutez le SAST (2-3 règles bloquantes au plus), puis l’analyse des dépendances. En 4-6 semaines, vous avez un pipeline mature qui détecte 80 % des vulnérabilités les plus courantes avant qu’elles n’atteignent la production.
Sur le même sujet
- Platform Engineering : construire une plateforme développeur interne — Intégrer vos pipelines DevSecOps dans une IDP : templates Backstage avec security gates pré-configurés, déploiement GitOps via ArgoCD.
- IA agentique en Java : construire des agents autonomes — Les mêmes principes de validation stricte des entrées/sorties s’appliquent aux outils d’un agent IA en production.
- WebAssembly et Java : GraalVM et TeaVM — Les images GraalVM distroless réduisent la surface d’attaque à scanner par Trivy : moins de packages = moins de CVEs.
Amine MEGDICHE
Développeur AEM & Java Full Stack — Freelance depuis 2013