20. Analyse de sécurité dans le pipeline (DevSecOps)#
La sécurité du logiciel a longtemps été traitée comme une étape finale : un audit réalisé avant la mise en production, souvent sous pression temporelle, avec des résultats difficiles à intégrer sans remettre en cause des décisions d’architecture prises des mois plus tôt. Le mouvement DevSecOps inverse cette logique en intégrant les contrôles de sécurité à chaque étape du cycle de développement — du commit initial jusqu’au déploiement en production.
Ce chapitre couvre les outils et pratiques qui constituent une posture DevSecOps moderne : analyse statique du code, tests dynamiques, scan de conteneurs et de dépendances, génération de SBOM, sécurisation de la chaîne d’approvisionnement logicielle, et politiques de gouvernance exprimées en code.
Shift-left security : anticiper pour réduire les coûts#
Le principe du shift-left (décalage vers la gauche sur l’axe temporel du cycle de développement) repose sur une observation économique simple : plus un défaut de sécurité est détecté tôt, moins il coûte à corriger.
Un problème découvert en phase de développement (lint, pre-commit hook) prend quelques minutes à traiter. Le même problème détecté en intégration continue nécessite un cycle de correctif complet. Découvert en production, il implique un incident, un rollback, une post-mortem et parfois une notification réglementaire.
Le shift-left ne signifie pas abandonner les contrôles tardifs, mais construire plusieurs lignes de défense :
Poste de travail : pre-commit hooks, linters de sécurité, détection de secrets (
gitleaks,detect-secrets)Pull Request / Merge Request : SAST, audit de dépendances, scan d’images
Branche principale : DAST sur environnement de staging, scan SBOM
Production : monitoring runtime, détection d’anomalies comportementales
Shift-left n’est pas « security as a blocker »
L’objectif est de donner aux développeurs des retours rapides et actionnables, pas de bloquer chaque pipeline pour une CVE de sévérité LOW. La politique doit définir des seuils clairs : bloquer sur CRITICAL, alerter sur HIGH, reporter sur MEDIUM et LOW.
SAST — Analyse statique du code source#
Le Static Application Security Testing (SAST) analyse le code source sans l’exécuter, à la recherche de patterns vulnérables : injections SQL, XSS, credentials en dur, désérialisation non sécurisée, mauvaise gestion des erreurs, etc.
Semgrep#
Semgrep est un moteur d’analyse statique multilangage basé sur des règles exprimées en YAML. Sa syntaxe de pattern ressemble au code lui-même, ce qui facilite l’écriture de règles personnalisées.
# .semgrep/custom-rules.yml
rules:
- id: hardcoded-aws-access-key
patterns:
- pattern: |
$X = "AKIA..."
message: "Clé d'accès AWS potentiellement en dur dans le code"
languages: [python, javascript, typescript]
severity: ERROR
metadata:
category: security
cwe: "CWE-798: Use of Hard-coded Credentials"
- id: sql-injection-format-string
patterns:
- pattern: |
$CURSOR.execute("..." % $INPUT)
- pattern: |
$CURSOR.execute("..." + $INPUT)
message: "Concaténation directe dans une requête SQL — risque d'injection"
languages: [python]
severity: ERROR
fix: "Utiliser des requêtes paramétrées : cursor.execute(query, (param,))"
- id: jwt-none-algorithm
pattern: |
jwt.decode($TOKEN, options={"verify_signature": False, ...})
message: "Vérification de signature JWT désactivée"
languages: [python]
severity: WARNING
Intégration dans GitHub Actions :
# .github/workflows/sast.yml
name: SAST Semgrep
on: [push, pull_request]
jobs:
semgrep:
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
- name: Scan avec les règles officielles + custom
run: |
semgrep \
--config=p/python \
--config=p/secrets \
--config=.semgrep/custom-rules.yml \
--sarif \
--output=semgrep.sarif \
--error
- name: Upload résultats SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
CodeQL (GitHub Advanced Security)#
CodeQL est le moteur SAST de GitHub. Il construit un graphe sémantique du code (data flow, control flow, call graph) et exécute des requêtes sur ce graphe pour détecter des vulnérabilités complexes que les simples pattern-matchers ne peuvent pas trouver — par exemple, une donnée utilisateur qui traverse plusieurs fonctions avant d’atteindre un sink dangereux.
# .github/workflows/codeql.yml
name: CodeQL
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "0 3 * * 1"
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
strategy:
matrix:
language: [python, javascript]
steps:
- uses: actions/checkout@v4
- name: Initialiser CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Analyser
uses: github/codeql-action/analyze@v3
DAST — Tests dynamiques de sécurité#
Le Dynamic Application Security Testing (DAST) teste l’application en cours d’exécution en simulant des attaques réelles : injections, traversal de répertoire, mauvaise configuration TLS, headers de sécurité manquants, etc. Il nécessite un environnement de staging accessible.
OWASP ZAP en mode CLI#
ZAP (Zed Attack Proxy) est l’outil DAST open-source de référence de l’OWASP. Son mode automation permet de l’intégrer dans un pipeline CI sans interface graphique.
# .github/workflows/dast.yml
name: DAST OWASP ZAP
on:
push:
branches: [main]
jobs:
zap-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Démarrer l'application de staging
run: docker compose -f docker-compose.staging.yml up -d
env:
DATABASE_URL: ${{ secrets.STAGING_DB_URL }}
- name: Attendre que l'application soit prête
run: |
for i in $(seq 1 30); do
curl -sf http://localhost:8080/health && break
sleep 2
done
- name: Scan ZAP (mode baseline)
uses: zaproxy/action-baseline@v0.11.0
with:
target: "http://localhost:8080"
rules_file_name: ".zap/rules.tsv"
cmd_options: "-a"
fail_action: true
- name: Upload rapport HTML
uses: actions/upload-artifact@v4
with:
name: zap-report
path: report_html.html
Scan actif vs passif
ZAP propose deux modes : le scan passif observe le trafic sans envoyer de requêtes supplémentaires (rapide, non destructif), et le scan actif envoie des payloads d’attaque (plus lent, peut modifier des données). En CI, le scan baseline combine les deux avec des heuristiques de sécurité.
Scan de conteneurs#
Trivy#
Trivy est l’outil de scan polyvalent d’Aqua Security. Il peut analyser des images Docker, des filesystems, des dépôts git, des fichiers IaC (Terraform, Kubernetes), et générer des SBOM.
# trivy.yaml — configuration centrale
scan:
scanners:
- vuln
- secret
- misconfig
severity:
- CRITICAL
- HIGH
vulnerability:
ignore-unfixed: true
exit-code: 1
ignore:
- id: CVE-2023-12345
reason: "Composant non exposé dans notre contexte"
expired-at: "2025-06-01"
Intégration GitHub Actions avec génération de SBOM :
- name: Scan Trivy image
uses: aquasecurity/trivy-action@master
with:
image-ref: "myregistry/myapp:${{ github.sha }}"
format: "sarif"
output: "trivy-results.sarif"
severity: "CRITICAL,HIGH"
exit-code: "1"
ignore-unfixed: true
trivyignores: ".trivyignore"
- name: Scan Trivy filesystem (IaC)
uses: aquasecurity/trivy-action@master
with:
scan-type: "fs"
scan-ref: "."
scanners: "misconfig,secret"
format: "table"
- name: Générer SBOM CycloneDX
uses: aquasecurity/trivy-action@master
with:
image-ref: "myregistry/myapp:${{ github.sha }}"
format: "cyclonedx"
output: "sbom.cyclonedx.json"
Grype (Anchore) et Snyk sont des alternatives : Grype est particulièrement apprécié pour son intégration avec Syft (génération de SBOM), Snyk pour son interface et ses intégrations IDE.
Scan de dépendances#
Dependabot (GitHub)#
Dependabot analyse les fichiers de manifeste (requirements.txt, package.json, go.mod, Dockerfile…) et ouvre automatiquement des Pull Requests pour les mises à jour de sécurité.
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
groups:
django:
patterns: ["django*"]
ignore:
- dependency-name: "numpy"
versions: ["2.x"]
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
- "docker"
Renovate (multi-plateforme)#
Renovate est plus configurable que Dependabot et supporte davantage d’écosystèmes (y compris Helm charts, modules Terraform, GitHub Actions).
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", ":dependencyDashboard"],
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"automerge": true,
"automergeType": "pr"
},
{
"matchPackageNames": ["python"],
"allowedVersions": "3.11.x"
}
],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"]
},
"prConcurrentLimit": 3,
"schedule": ["before 6am on Monday"]
}
SBOM — Software Bill of Materials#
Un SBOM est une liste exhaustive des composants d’un logiciel : bibliothèques, versions, licences, dépendances transitives. Il joue le rôle d’une liste d’ingrédients qui permet de répondre rapidement à la question « sommes-nous affectés par la CVE X ? » sans scanner chaque image manuellement.
Formats : SPDX et CycloneDX#
SPDX (Software Package Data Exchange) est un standard ISO (ISO 5962:2021) maintenu par la Linux Foundation. Il est orienté gestion de licences mais couvre aussi les vulnérabilités.
CycloneDX est un standard OWASP orienté sécurité, plus léger et mieux adapté à l’automatisation DevSecOps. Il supporte les formats JSON, XML et Protocol Buffers.
Génération avec Syft#
Syft (Anchore) génère des SBOM depuis des images, des filesystems ou des archives :
# Générer un SBOM CycloneDX depuis une image
syft myregistry/myapp:latest -o cyclonedx-json=sbom.json
# Depuis un filesystem local
syft dir:./src -o spdx-json=sbom-src.spdx.json
# Combiner avec Grype pour la vulnérabilité
syft myapp:latest -o json | grype
Obligation réglementaire
L’Executive Order américain 14028 (2021) rend le SBOM obligatoire pour les logiciels vendus au gouvernement américain. La directive NIS2 européenne pousse dans la même direction. Mettre en place la génération automatique de SBOM dès maintenant est une décision stratégique, pas seulement technique.
Sécurité de la chaîne d’approvisionnement (supply chain)#
Les attaques de type supply chain (SolarWinds, log4shell, xz-utils) ciblent les dépendances et les outils de build plutôt que l’application elle-même. Plusieurs cadres et outils adressent ce problème.
SLSA (Supply chain Levels for Software Artifacts)#
SLSA définit quatre niveaux de maturité pour la sécurité de la chaîne d’approvisionnement :
SLSA 0 : aucune garantie
SLSA 1 : le processus de build est documenté et automatisé
SLSA 2 : le build est hébergé et produit des attestations signées
SLSA 3 : le build est hermétique, reproductible, et les sources sont vérifiées
La plupart des projets open-source sérieux visent SLSA 3. Pour les projets internes, SLSA 2 représente une bonne cible réaliste.
Sigstore / Cosign — Signature d’images#
Cosign (partie du projet Sigstore) permet de signer des images Docker et de vérifier ces signatures. La clé privée peut être stockée dans un KMS ou gérée par Sigstore en mode keyless (via OIDC, sans gestion de clés manuelles).
# Signature keyless (utilise l'identité OIDC du runner CI)
cosign sign --yes myregistry/myapp:latest
# Vérification
cosign verify \
--certificate-identity-regexp="https://github.com/myorg/myrepo/.*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
myregistry/myapp:latest
# Attacher un SBOM à l'image signée
cosign attach sbom --sbom sbom.cyclonedx.json myregistry/myapp:latest
cosign sign --yes --attachment sbom myregistry/myapp:latest
Rekor — Transparency log#
Rekor est le composant de Sigstore qui maintient un journal d’audit immuable (transparency log) de toutes les signatures. Chaque signature enregistrée dans Rekor reçoit un UUID et un timestamp certifié. Cela permet d’auditer rétrospectivement quand et par qui une image a été signée.
# Chercher les entrées Rekor pour une image
rekor-cli search --artifact myregistry/myapp:latest
# Vérifier une entrée spécifique
rekor-cli get --uuid <uuid>
Policy as Code#
OPA / Gatekeeper#
Open Policy Agent (OPA) est un moteur de politique généraliste. Gatekeeper est son intégration native dans Kubernetes : il agit comme un Admission Controller qui intercepte chaque ressource avant qu’elle soit créée ou modifiée et la valide contre des politiques exprimées en Rego.
# policies/require-resource-limits.rego
package kubernetes.admission
import future.keywords.if
import future.keywords.in
violation[{"msg": msg}] if {
container := input.review.object.spec.containers[_]
not container.resources.limits
msg := sprintf("Le conteneur '%v' n'a pas de resource limits définies", [container.name])
}
violation[{"msg": msg}] if {
container := input.review.object.spec.containers[_]
not container.resources.limits.memory
msg := sprintf("Le conteneur '%v' n'a pas de limite mémoire", [container.name])
}
Conftest permet de tester des manifestes YAML/JSON localement contre des politiques Rego, sans cluster Kubernetes :
# Valider un manifeste Kubernetes avant déploiement
conftest test deployment.yaml --policy policies/
# Valider une configuration Terraform
conftest test plan.json --policy policies/terraform/
Kyverno#
Kyverno est une alternative à OPA/Gatekeeper native Kubernetes : ses politiques sont elles-mêmes des ressources Kubernetes (CRD), ce qui les rend plus accessibles que Rego pour les équipes sans expérience fonctionnelle.
Kyverno supporte trois types d’opérations :
Validation : refuser les ressources non conformes
Mutation : modifier automatiquement les ressources (ajouter des labels, des resource limits par défaut)
Génération : créer des ressources dérivées (NetworkPolicy, ServiceAccount)
# kyverno-policy-no-latest-tag.yml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
annotations:
policies.kyverno.io/title: Interdire le tag :latest
policies.kyverno.io/description: >
Le tag :latest rend les déploiements non reproductibles.
Utiliser des tags immuables (SHA digest ou version sémantique).
spec:
validationFailureAction: Enforce
background: true
rules:
- name: require-image-tag
match:
any:
- resources:
kinds: [Pod]
validate:
message: "L'image doit utiliser un tag explicite, pas :latest"
pattern:
spec:
containers:
- image: "!*:latest"
initContainers:
- image: "!*:latest"
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-provenance-labels
spec:
rules:
- name: add-git-sha
match:
any:
- resources:
kinds: [Deployment, StatefulSet]
mutate:
patchStrategicMerge:
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
Visualisations#
Distribution des CVEs et seuils CVSS#
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
rng = np.random.default_rng(42)
# Simulation d'un portfolio de 250 CVEs avec distribution réaliste
scores = np.concatenate([
rng.normal(loc=2.5, scale=0.8, size=60), # LOW
rng.normal(loc=5.0, scale=1.0, size=100), # MEDIUM
rng.normal(loc=7.2, scale=0.7, size=65), # HIGH
rng.normal(loc=9.1, scale=0.5, size=25), # CRITICAL
])
scores = np.clip(scores, 0.1, 10.0)
# Zones de sévérité CVSS v3
zones = {
"LOW\n(0–3.9)": ("#e8f5e9", 0.0, 4.0),
"MEDIUM\n(4–6.9)": ("#fff3e0", 4.0, 7.0),
"HIGH\n(7–8.9)": ("#fce4ec", 7.0, 9.0),
"CRITICAL\n(9–10)": ("#ffebee", 9.0, 10.0),
}
seuils = [
(4.0, "#4caf50"),
(7.0, "#ff9800"),
(9.0, "#f44336"),
]
fig, ax = plt.subplots(figsize=(11, 5))
for label, (couleur, x_min, x_max) in zones.items():
ax.axvspan(x_min, x_max, alpha=0.25, color=couleur, zorder=0)
ax.text(
(x_min + x_max) / 2, 42,
label,
ha="center", va="top",
fontsize=9, color="#555555",
fontweight="bold",
)
ax.hist(scores, bins=40, color="#5c85d6", edgecolor="white", linewidth=0.5, alpha=0.85, zorder=2)
for x_val, couleur in seuils:
ax.axvline(x=x_val, color=couleur, linewidth=2.0, linestyle="--", zorder=3)
n_critical = np.sum(scores >= 9.0)
n_high = np.sum((scores >= 7.0) & (scores < 9.0))
n_medium = np.sum((scores >= 4.0) & (scores < 7.0))
n_low = np.sum(scores < 4.0)
stats_text = (
f"CRITICAL : {n_critical} ({n_critical/len(scores)*100:.0f}%)\n"
f"HIGH : {n_high} ({n_high/len(scores)*100:.0f}%)\n"
f"MEDIUM : {n_medium} ({n_medium/len(scores)*100:.0f}%)\n"
f"LOW : {n_low} ({n_low/len(scores)*100:.0f}%)"
)
ax.text(
0.98, 0.97, stats_text,
transform=ax.transAxes,
ha="right", va="top",
fontsize=9, family="monospace",
bbox=dict(boxstyle="round,pad=0.4", facecolor="white", edgecolor="#cccccc", alpha=0.9),
)
ax.set_xlabel("Score CVSS v3", fontsize=11)
ax.set_ylabel("Nombre de CVEs", fontsize=11)
ax.set_title(
f"Distribution des scores CVSS — portfolio de {len(scores)} CVEs simulées",
fontsize=13, pad=14,
)
ax.set_xlim(0, 10)
plt.show()
ROI du shift-left — Coût de correction selon le moment de détection#
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
phases = [
"Poste\ndéveloppeur",
"Pull\nRequest",
"CI/CD\nPipeline",
"Staging /\nPré-prod",
"Production",
]
# Coût relatif de correction (base 1 en développement)
couts = [1, 5, 14, 30, 150]
couleurs = ["#43a047", "#8bc34a", "#ffa726", "#ef5350", "#b71c1c"]
fig, ax = plt.subplots(figsize=(10, 5))
bars = ax.bar(phases, couts, color=couleurs, edgecolor="white", linewidth=0.8, width=0.55, zorder=2)
for bar, cout in zip(bars, couts):
ax.text(
bar.get_x() + bar.get_width() / 2,
bar.get_height() + 2,
f"×{cout}",
ha="center", va="bottom",
fontsize=11, fontweight="bold", color="#333333",
)
ax.annotate(
"← Détecter plus tôt = coût divisé par ~150",
xy=(0.03, 0.93),
xycoords="axes fraction",
fontsize=9.5, color="#1a237e", style="italic",
)
ax.set_ylabel("Coût relatif de correction (base 1)", fontsize=11)
ax.set_title(
"Coût de correction d'une vulnérabilité selon le moment de détection",
fontsize=13, pad=14,
)
ax.set_ylim(0, 175)
plt.show()
Graphe de dépendances SBOM#
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import networkx as nx
import seaborn as sns
sns.set_theme(style="white", font_scale=1.0)
G = nx.DiGraph()
noeuds = {
"myapp:1.0.0": "app",
"django:4.2.7": "lib",
"requests:2.31.0": "lib",
"psycopg2:2.9.9": "lib",
"celery:5.3.4": "lib",
"urllib3:2.1.0": "lib",
"certifi:2023.11.17": "lib",
"kombu:5.3.3": "lib",
"openssl:3.1.4": "os",
"libpq:15.4": "os",
"python:3.11.6": "os",
"libc:2.36": "os",
}
aretes = [
("myapp:1.0.0", "django:4.2.7"),
("myapp:1.0.0", "requests:2.31.0"),
("myapp:1.0.0", "psycopg2:2.9.9"),
("myapp:1.0.0", "celery:5.3.4"),
("requests:2.31.0", "urllib3:2.1.0"),
("requests:2.31.0", "certifi:2023.11.17"),
("celery:5.3.4", "kombu:5.3.3"),
("urllib3:2.1.0", "openssl:3.1.4"),
("psycopg2:2.9.9", "libpq:15.4"),
("django:4.2.7", "python:3.11.6"),
("openssl:3.1.4", "libc:2.36"),
("libpq:15.4", "libc:2.36"),
("python:3.11.6", "libc:2.36"),
]
G.add_nodes_from(noeuds.keys())
G.add_edges_from(aretes)
palette = {"app": "#1565c0", "lib": "#388e3c", "os": "#f57c00"}
couleurs_noeuds = [palette[noeuds[n]] for n in G.nodes()]
tailles_noeuds = [
2800 if noeuds[n] == "app" else 1600 if noeuds[n] == "lib" else 1200
for n in G.nodes()
]
fig, ax = plt.subplots(figsize=(13, 7))
ax.set_facecolor("#fafafa")
pos = nx.spring_layout(G, seed=7, k=2.2)
nx.draw_networkx_nodes(G, pos, node_color=couleurs_noeuds, node_size=tailles_noeuds, alpha=0.88, ax=ax)
nx.draw_networkx_edges(
G, pos,
edge_color="#aaaaaa",
arrows=True, arrowsize=16, width=1.4,
connectionstyle="arc3,rad=0.05",
ax=ax,
)
nx.draw_networkx_labels(G, pos, font_size=7.5, font_color="white", font_weight="bold", ax=ax)
legende = [
mpatches.Patch(color=palette["app"], label="Application"),
mpatches.Patch(color=palette["lib"], label="Bibliothèque"),
mpatches.Patch(color=palette["os"], label="Composant OS / système"),
]
ax.legend(handles=legende, loc="lower left", framealpha=0.9, fontsize=9)
ax.set_title("SBOM — Graphe de dépendances (myapp:1.0.0)", fontsize=13, pad=14)
ax.axis("off")
plt.show()
Résumé#
Ce chapitre a couvert l’ensemble des pratiques qui constituent un pipeline DevSecOps moderne.
Shift-left : déplacer les contrôles de sécurité vers les phases les plus précoces du développement réduit le coût de correction d’un facteur pouvant atteindre 150× entre le poste du développeur et la production. La clé est d’intégrer les outils dans les flux de travail existants (IDE, pre-commit, CI) sans créer de friction excessive.
SAST avec Semgrep et CodeQL : l’analyse statique détecte les patterns vulnérables dans le code source. Semgrep brille par la facilité d’écriture de règles métier personnalisées ; CodeQL par sa capacité à tracer les flux de données à travers l’ensemble du code. Les deux sont complémentaires.
DAST avec OWASP ZAP : les tests dynamiques valident le comportement réel de l’application déployée — configurations TLS, headers de sécurité, comportements face aux injections — et détectent des classes de vulnérabilités invisibles à l’analyse statique.
Trivy, Grype, Snyk : le scan de conteneurs doit couvrir les CVEs des packages OS et des bibliothèques applicatives, mais aussi les misconfigurations d’images (utilisateur root, capabilities inutiles, secrets en dur).
SBOM avec Syft : la génération systématique d’un SBOM en formats CycloneDX ou SPDX permet de répondre en minutes à la question « sommes-nous affectés ? » lors de la découverte d’une nouvelle CVE, et répond aux exigences réglementaires émergentes.
Sigstore / Cosign / Rekor : la signature d’images et l’enregistrement dans un transparency log constituent les fondations d’une chaîne d’approvisionnement vérifiable, alignée avec les niveaux SLSA 2 et 3.
OPA/Gatekeeper et Kyverno : le Policy as Code transforme les règles de sécurité en artefacts versionnés, testables et déployables comme n’importe quel autre code. Gatekeeper via Rego offre une expressivité maximale ; Kyverno via des CRD Kubernetes est plus accessible pour les équipes orientées Ops.
Recommandations de mise en œuvre par ordre de priorité
Activer Dependabot ou Renovate — zéro configuration, impact immédiat sur les dépendances vulnérables
Intégrer Trivy dans le pipeline CI pour bloquer sur CRITICAL
Ajouter Semgrep avec les règles communautaires (p/python, p/javascript, p/secrets)
Générer des SBOM à chaque build et les attacher aux releases
Mettre en place Cosign pour signer les images de production
Déployer Kyverno (ou Gatekeeper) sur le cluster avec des politiques de baseline CIS