DevOpsFacile
AccueilParcours de formationCertificationsModulesCheat SheetÀ Propos
DevOpsFacile

Une plateforme d'apprentissage complète pour maîtriser les pratiques DevOps modernes, du débutant à l'expert.

contact@devopsfacile.fr

Formation

  • Parcours de formation
  • Modules

Informations

  • À Propos
  • Conditions d'utilisation
  • Confidentialité
  • Mentions légales

© 2026 DevOps Facile. Tous droits réservés.

Fait avec pour la communauté DevOps

ModulesSécurité DevOps - Avancé05 - Capstone : Architecture DevSecOps avancée

Détails

  • 2h
  • Avancé

Objectifs

  • Assembler une architecture DevSecOps complète avec Vault, cosign, RBAC et OPA
  • Implémenter une policy as code qui bloque les images non signées
  • Configurer Vault pour injecter des secrets dans les pods Kubernetes
  • Concevoir et documenter un threat model STRIDE pour une application
Module Sécurité DevOps - Avancé

Exercice 05 : Capstone - Architecture DevSecOps Avancée

🎯 Objectifs

À la fin de cet exercice, vous serez capable de :

  • ✅ Configurer Vault Agent Injector pour les secrets Kubernetes
  • ✅ Écrire une politique OPA qui refuse les images non signées avec cosign
  • ✅ Construire un pipeline end-to-end : build → sign → verify → deploy
  • ✅ Réaliser un threat model STRIDE simplifié sur votre architecture
  • ✅ Rédiger un runbook de réponse à incident pour une CVE critique

Durée estimée : 2h

Difficulté : ⭐⭐⭐⭐⭐ (Expert)

Prérequis : Exercices 01 à 04 complétés, cluster Kubernetes avec Gatekeeper


📖 Contexte

Vous êtes architecte DevSecOps. Une startup vous demande de concevoir et implémenter leur infrastructure de sécurité de zéro. L'objectif : "Secure by Default" - aucune configuration non sécurisée ne peut atteindre la production.


📋 Énoncé

Construisez une infrastructure DevSecOps complète en 5 modules interconnectés.


🧭 Déroulement de l'exercice

Module 1 : Vault comme source unique de vérité pour les secrets

Étape 1 : Configurer Vault pour Kubernetes

bash
# Prérequis : Vault accessible depuis le cluster K8s
# Pour cet exercice, Vault tourne dans le cluster lui-même

# Installer Vault avec Helm
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault \
  --namespace vault \
  --create-namespace \
  --set "server.dev.enabled=true" \
  --set "injector.enabled=true"

kubectl wait --for=condition=ready pod/vault-0 -n vault --timeout=60s

Étape 2 : Configurer l'authentification Kubernetes

bash
# Exec dans le pod Vault
kubectl exec -n vault vault-0 -- /bin/sh << 'EOF'
# Activer l'auth Kubernetes
vault auth enable kubernetes

# Configurer avec les infos du cluster
vault write auth/kubernetes/config \
  kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"

# Créer un secret de base de données
vault secrets enable -path=secret kv-v2
vault kv put secret/production/database \
  username=app_user \
  password="$(openssl rand -base64 24)"

# Créer une politique d'accès
vault policy write app-policy - << 'POLICY'
path "secret/data/production/*" {
  capabilities = ["read"]
}
POLICY

# Créer un rôle Kubernetes
vault write auth/kubernetes/role/app-role \
  bound_service_account_names=app-sa \
  bound_service_account_namespaces=production \
  policies=app-policy \
  ttl=1h
EOF

Étape 3 : Déploiement avec injection automatique des secrets

yaml
# app-deployment.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
  namespace: production

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-app
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: secure-app
  template:
    metadata:
      labels:
        app: secure-app
      annotations:
        # Annotations Vault Agent Injector
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "app-role"
        vault.hashicorp.com/agent-inject-secret-database: "secret/data/production/database"
        vault.hashicorp.com/agent-inject-template-database: |
          {{- with secret "secret/data/production/database" -}}
          DB_USERNAME={{ .Data.data.username }}
          DB_PASSWORD={{ .Data.data.password }}
          {{- end }}
    spec:
      serviceAccountName: app-sa
      containers:
        - name: app
          image: ghcr.io/votre-org/secure-app:latest
          command: ["/bin/sh", "-c"]
          args:
            - |
              # Vault injecte les secrets dans /vault/secrets/
              source /vault/secrets/database
              echo "Connecté en tant que: $DB_USERNAME"
              # Lancer l'application réelle ici
              sleep infinity
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
          resources:
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 50m
              memory: 64Mi
bash
kubectl apply -f app-deployment.yaml

# Vérifier que les secrets sont injectés
kubectl exec -n production deployment/secure-app -c app -- cat /vault/secrets/database

Module 2 : OPA - Interdire les images non signées

Créer une ConstraintTemplate qui vérifie les signatures cosign

bash
cat > constraint-signed-images.yaml << 'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiresignedimages
spec:
  crd:
    spec:
      names:
        kind: K8sRequireSignedImages
      validation:
        openAPIV3Schema:
          properties:
            allowedIssuers:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiresignedimages

        # Note : La vérification cosign complète nécessite un webhook externe
        # (ex: Connaisseur ou Sigstore Policy Controller)
        # Cette règle vérifie le format de l'image (pas de :latest)
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]

          # Interdire l'utilisation du tag :latest
          endswith(container.image, ":latest")

          msg := sprintf(
            "Container '%v' : l'image '%v' utilise le tag ':latest'. Utilisez un tag immuable (SHA ou version).",
            [container.name, container.image]
          )
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]

          # Vérifier qu'un digest SHA est utilisé OU un tag de version
          not contains(container.image, "@sha256:")
          not regex.match(`:.+\..+$|:v?\d+\.\d+`, container.image)
          not endswith(container.image, ":latest")  # Déjà géré ci-dessus

          msg := sprintf(
            "Container '%v' : l'image '%v' doit utiliser un digest SHA256 ou un tag de version sémantique.",
            [container.name, container.image]
          )
        }
EOF

cat > constraint-apply-signed.yaml << 'EOF'
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireSignedImages
metadata:
  name: require-signed-images
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment", "StatefulSet", "DaemonSet"]
    namespaces: ["production"]
  enforcementAction: deny
EOF

kubectl apply -f constraint-signed-images.yaml constraint-apply-signed.yaml

Module 3 : Pipeline CI/CD avec signature et vérification

yaml
# .github/workflows/secure-pipeline.yml
name: Secure Build and Deploy

on:
  push:
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-sign:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write  # Pour cosign keyless

    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
      image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}

    steps:
      - uses: actions/checkout@v4
      - uses: sigstore/cosign-installer@v3
      - uses: anchore/sbom-action/download-syft@v0

      - uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Build avec tag immuable (SHA)
      - name: Build et push
        id: build
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}

      # Scanner AVANT de signer (ne pas signer une image vulnérable)
      - name: Scanner l'image (bloquant)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
          exit-code: '1'
          severity: 'CRITICAL'
          ignore-unfixed: true

      # Générer SBOM
      - uses: anchore/sbom-action@v0
        with:
          image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
          output-file: sbom.spdx.json

      # Signer l'image (keyless via GitHub OIDC)
      - name: Signer l'image
        run: cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}

      # Attacher SBOM
      - name: Attacher SBOM
        run: |
          cosign attest --yes \
            --type spdxjson \
            --predicate sbom.spdx.json \
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}

  deploy:
    needs: build-and-sign
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Vérifier la signature avant déploiement
        run: |
          cosign verify \
            --certificate-identity-regexp "https://github.com/${{ github.repository }}/" \
            --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
            ${{ needs.build-and-sign.outputs.image-ref }}
          echo "✅ Signature vérifiée - déploiement autorisé"

      - name: Déployer avec le digest immuable
        run: |
          # Utiliser le digest SHA, jamais un tag mutable
          kubectl set image deployment/secure-app \
            app=${{ needs.build-and-sign.outputs.image-ref }} \
            -n production

Module 4 : Threat Model STRIDE

Réalisez une analyse STRIDE simplifiée de votre architecture :

markdown
# Threat Model - Architecture DevSecOps

## Application : API Backend + Base de données

### Diagramme de flux de données

User → [HTTPS] → API Gateway → [internal] → App Container → [PostgreSQL] → DB

↑

Vault Agent Injector


## Analyse STRIDE

| Menace | Composant | Exemple d'attaque | Mitigation |
|--------|-----------|-------------------|------------|
| **S**poofing | API | Usurpation d'identité JWT | OIDC + rotation des clés |
| **T**ampering | Image Docker | Modification après build | cosign + vérification au déploiement |
| **R**epudiation | Actions CI/CD | "Je n'ai pas fait ce commit" | Rekor (journal immuable) |
| **I**nformation Disclosure | Secrets | Clé API dans les logs | Vault + pas d'env vars littérales |
| **D**enial of Service | API | Surcharge des pods | Resource limits + HPA |
| **E**levation of Privilege | Pods | Container escape → root | runAsNonRoot + RBAC |

## Risques résiduels
- Compromission du compte GitHub (MFA obligatoire)
- Vault dev mode en prod (configurer HA avec TLS)

Module 5 : Runbook de réponse à incident - CVE critique

markdown
# Runbook : CVE critique dans une dépendance

## Déclencheur
- Alerte Dependabot CVE CVSS >= 9.0
- Alert Trivy CRITICAL dans une image en production

## Étapes (objectif : < 2h de résolution)

### T+0 : Détection
1. Vérifier la portée : `trivy image --severity CRITICAL mon-image:prod`
2. Identifier si la CVE est exploitable dans notre contexte
3. Ouvrir un incident Slack/ticket avec priorité P1

### T+15min : Containment
1. Si exploitable : scaler à 0 le déploiement (`kubectl scale --replicas=0`)
2. Sinon : continuer en observation

### T+30min : Remediation
1. Mettre à jour le package dans `package.json` / `requirements.txt`
2. Rebuilder l'image avec la version patchée
3. Trivy scan de la nouvelle image (0 CRITICAL requis)
4. Déploiement via pipeline CI normal

### T+90min : Vérification
1. `trivy image mon-image:nouvelle-version` → 0 CRITICAL
2. Tests de non-régression passent
3. Déploiement en production validé

### T+120min : Post-mortem
1. Documenter la CVE, l'impact, la timeline
2. Mise à jour du runbook si nécessaire
bash
# Sauvegarder le runbook
mkdir -p docs/runbooks
# Copier le contenu du runbook dans docs/runbooks/cve-critique.md
git add .
git commit -m "security: architecture DevSecOps complète - Vault, cosign, OPA, runbook CVE"
git push

✅ Vérification du résultat

  • Vault Agent Injector injecte les secrets dans le pod /vault/secrets/
  • Le pipeline CI refuse de signer une image avec CRITICAL
  • Le pipeline vérifie la signature avant le déploiement
  • OPA Gatekeeper refuse les images avec tag :latest en production
  • Le threat model STRIDE couvre les 6 catégories de menaces
  • Le runbook CVE définit des étapes actionnables avec une timeline

💡 À retenir

L'architecture DevSecOps avancée forme une boucle de confiance :

Code → SAST (CodeQL) → Build → SBOM → Scan (Trivy) → Signe (cosign)
                                                              ↓
Production ← Vault (secrets) ← RBAC ← OPA ← Vérifie signature

Chaque maillon valide le suivant. Si un maillon est compromis, les autres l'arrêtent.

Principe fondamental : La sécurité ne doit pas être une case à cocher à la fin du développement. Elle s'intègre à chaque étape, de façon automatique et non optionnelle.


✨ Solution Complète

La solution complète est définie dans les modules 1 à 5 ci-dessus. La clé est l'intégration de chaque composant dans la pipeline CI/CD :

  1. Vault : secrets injectés au runtime, jamais dans les manifestes
  2. cosign : signature cryptographique dans la CI, vérification au déploiement
  3. Trivy : scan bloquant avant la signature
  4. OPA/Gatekeeper : admission control Kubernetes
  5. Runbook : processus défini avant qu'une crise arrive
Retour au module

Sur cette page

  • 🎯 Objectifs
  • 📖 Contexte
  • 📋 Énoncé
  • 🧭 Déroulement de l'exercice
  • Module 1 : Vault comme source unique de vérité pour les secrets
  • Module 2 : OPA - Interdire les images non signées
  • Module 3 : Pipeline CI/CD avec signature et vérification
  • Module 4 : Threat Model STRIDE
  • Application : API Backend + Base de données
  • Diagramme de flux de données
  • Analyse STRIDE
  • Risques résiduels
  • Module 5 : Runbook de réponse à incident - CVE critique
  • Déclencheur
  • Étapes (objectif : < 2h de résolution)
  • T+0 : Détection
  • T+15min : Containment
  • T+30min : Remediation
  • T+90min : Vérification
  • T+120min : Post-mortem
  • ✅ Vérification du résultat
  • 💡 À retenir
  • ✨ Solution Complète