Aller au contenu
DevOps 10 min de lecture

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.

DevSecOpsSécuritéCI/CDJavaSASTDASTGitHub ActionsOWASPDependabot

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

Service lié

Développement Java Full Stack & APIs

Découvrir le service
Photo d'Amine MEGDICHE, auteur de l'article

Amine MEGDICHE

Développeur AEM & Java Full Stack — Freelance depuis 2013

Vous avez un projet sur ces sujets ?

Envoyez-moi un message pour qualifier votre besoin, vos contraintes techniques et le bon format d'intervention.

Cadrer mon besoin