Aller au contenu
  1. Articles/

Container Security : Trivy et Kyverno en production

Un cluster Kubernetes en production, c’est des dizaines d’images Docker qui tournent, des centaines de dépendances, et potentiellement des vulnérabilités connues. Laisser ça sans surveillance, c’est attendre l’incident.

Deux outils complémentaires couvrent la majorité des besoins : Trivy pour le scan et la détection, Kyverno pour les politiques d’admission. Le premier te montre les problèmes, le second les bloque.

Trivy — le scanner de vulnérabilités #

Trivy est open source (par Aqua Security), léger, et scanne tout : images Docker, fichiers filesystem, config Kubernetes, IaC (Terraform, CloudFormation)…

Installation #

# Via Helm sur le cluster
helm repo add aquasecurity https://aquasecurity.github.io/helm-charts
helm install trivy aquasecurity/trivy \
  --namespace trivy \
  --create-namespace

Scan d’une image #

# Scan basique
trivy image nginx:latest

# Format JSON pour automatisation
trivy image --format json --output report.json nginx:latest

La sortie :

nginx:latest (debian 12.8)
==========================
Total: 47 (unknown: 0, low: 12, medium: 23, high: 12, critical: 0)

Library                Type  Vulnerability    Severity  Status
--------------------- ----- --------------- --------- ------
libssl3               pkg   CVE-2024-12797   HIGH      Fixed
curl                  pkg   CVE-2024-12345  MEDIUM    Fixed
...

Trivy maintient une base de vulnérabilités à jour : NVD, GitHub Advisories, distributions Linux, bases Aqua, et même les CVE des principaux clouds.

Scan CI/CD #

Dans un pipeline GitLab :

# .gitlab-ci.yml
trivy-scan:
  stage: security
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE_URL
  variables:
    IMAGE_URL: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  allow_failure: true  # Ou pas, selon ta politique

--exit-code 1 fait échouer le job si des vulnérabilités HIGH/CRITICAL sont trouvées. allow_failure: true permet de garder le build fonctionnel tout en alertant.

Scan Kubernetes Admission Controller #

Trivy peut aussi bloquer les déploiements directement dans Kubernetes via un admission controller :

# Installation via Trivy Operator
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/trivy-operator/main/deploy/static/trivy-operator.yaml

L’operator crée automatiquement des rapports de vulnérabilité pour chaque pod déployé :

kubectl get vulnerabilityreports
NAME                REPOSITORY          AGE
pod-replicaset-xxx  mon-app:latest     2d

Les limites de Trivy #

Trivy détecte les vulnérabilités connues dans les packages, mais :

  • Ne bloque pas l’exécution sans admission controller
  • Ne contrôle pas le runtime (comportement du conteneur une fois lancé)
  • Faux positifs possibles sur des vulnérabilités non exploitables dans ton contexte

Pour le runtime, il faut Falco. Pour le contrôle des déploiements, il faut Kyverno.

Kyverno — les politiques d’admission #

Kyverno est un moteur de politiques pour Kubernetes. Contrairement à OPA/Gatekeeper qui utilise un DSL custom (Rego), Kyverno utilise des manifestes Kubernetes : si tu sais écrire un YAML, tu sais écrire une politique.

Installation #

# Via Helm
helm repo add kyverno https://kyverno.github.io/kyverno
helm install kyverno kyverno/kyverno \
  --namespace kyverno \
  --create-namespace \
  --values - <<EOF
backgroundController:
  replicas: 1
reportsController:
  replicas: 1
EOF

Vérifier :

kubectl get pods -n kyverno
NAME                          READY   STATUS
kyverno-admission-controller   1/1     Running
kyverno-reports-controller-0   1/1     Running
kyverno-background-controller  1/1     Running

Écrire une politique #

Une politique Kyverno est un ClusterPolicy ou une Policy (namespace-scoped). Exemple : interdire les conteneurs qui tournent en root :

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-root-user
spec:
  validationFailureAction: Enforce  # Bloque au lieu de juste alerter - Perso sur le cluster de dev -> Audit
  rules:
    - name: validate-runAsNonRoot
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "Les conteneurs ne doivent pas tourner en root."
        pattern:
          spec:
            containers:
              - (runAsNonRoot): true
            initContainers:
              - (runAsNonRoot): true

(runAsNonRoot): true — les parenthèses signifient “cette valeur doit exister et être true”. Sans parenthèses, ça vérifie juste la présence.

Valider avant d’appliquer #

Les politiques ont trois modes :

ModeComportement
AuditLog les violations, n’empêche pas le déploiement
EnforceBloque le déploiement si violation
spec:
  validationFailureAction: Audit  # Pour tester avant d'Enforce

Un Audit permet de valider que ta politique ne génère pas de faux positifs avant de passer en production.

Politiques utiles en production #

1. Interdire les latest tags #

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-image-tag
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-image-tag
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "Chaque image doit avoir un tag explicite (pas latest)."
        pattern:
          spec:
            containers:
              - image: "!*:latest"

Le ! signifie “ne doit pas correspondre au pattern”. *:latest interdit le tag latest.

2. Require resource limits #

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resources
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-limits
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "Les conteneurs doivent avoir des resource limits."
        pattern:
          spec:
            containers:
              - resources:
                  limits:
                    memory: "?*"
                    cpu: "?*"

"?*" signifie “au moins une valeur définie”.

3. Bloquer les privileged pods #

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged-containers
spec:
  validationFailureAction: Enforce
  rules:
    - name: privileged-containers
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "Les conteneurs privileged ne sont pas autorisés."
        pattern:
          spec:
            =(containers):
              - securityContext:
                  (privileged): "!*true"

=(containers) avec = signifie “s’il existe, alors”. Ça permet de ne pas bloquer les pods sans conteneurs (edge case).

4. Restreindre les capabilities Linux #

Par défaut, un conteneur a beaucoup de capabilities système. Interdire les plus dangereuses :

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-capabilities
spec:
  validationFailureAction: Enforce
  rules:
    - name: drop-capabilities
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "Les capabilities SYS_ADMIN, NET_ADMIN et SYS_MODULE sont interdites."
        deny:
          conditions:
            - key: "[SYS_ADMIN, NET_ADMIN, SYS_MODULE]"
              operator: AnyIn
              value: "{{ request.object.spec.[containers, initContainers, ephemeralContainers].[*].securityContext.capabilities.add[] }}"

5. Régénérer les secrets injectés #

Kyverno peut aussi muter les ressources. Exemple : ajouter un sidecar de monitoring automatiquement :

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-monitoring-sidecar
spec:
  mutation:
    rules:
      - name: add-envoy-sidecar
        match:
          resources:
            kinds:
              - Deployment
            namespaces:
              - production
        mutate:
          patchStrategicMerge:
            spec:
              template:
                spec:
                  containers:
                    - name: envoy
                      image: envoyproxy/envoy:latest
                      ports:
                        - containerPort: 9901
                          name: admin

Déployer dans production ? Kyverno ajoute automatiquement le sidecar.

Trivy + Kyverno : le combo #

Les deux outils se complètent :

TrivyKyverno
QuandBuild, admission, scan continuAdmission controller
QuoiVulnérabilités, config, IaCPolitiques (validate/mutate/generate)
ActionDétecteBloque ou modifie

Mon setup typique :

CI/CD Pipeline
  └─ Trivy scan (exit-code si HIGH/CRITICAL)
  └─ Push image

GitOps Deployment
  └─ ArgoCD détecte le nouveau manifest

Kubernetes Admission
  ├─ Kyverno valide (pas de latest, limits requis, pas de root…)
  └─ Trivy Operator scanne les images déployées

Admission avec Trivy Operator #

Le Trivy Operator peut aussi bloquer via Kyverno :

# Activer l'admission controller
helm upgrade --install trivy aquasecurity/trivy \
  --namespace trivy \
  --set trivy.operator.rbac.jobAnnotations."checks\.trivy\.dev/required"="true"

Les vulnérabilités HIGH/CRITICAL dans une image peuvent bloquer le déploiement.

Génération automatique #

Kyverno peut générer des ressources automatiquement. Exemple : créer un NetworkPolicy quand un Namespace est créé :

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: default-deny-network
spec:
  rules:
    - name: deny-all
      match:
        resources:
          kinds:
            - Namespace
      generate:
        apiVersion: networking.k8s.io/v1
        kind: NetworkPolicy
        name: deny-all
        namespace: "{{ request.object.metadata.name }}"
        synchronize: true
        data:
          spec:
            podSelector: {}
            policyTypes:
              - Ingress
              - Egress

Chaque nouveau namespace aura automatiquement une politique deny-all qui bloque tout le trafic.

Audit et rapports #

Kyverno génère des rapports d’audit :

# Voir les violations
kubectl get polr -A

NAMESPACE    POLICY               RESOURCE            RESULT   MESSAGE
production   require-resources    deployment/mon-app  Pass     -
staging      require-resources    deployment/mon-app  Fail     Les conteneurs...

Intégration avec Prometheus :

# Activer les métriques
kubectl get pods -n kyverno -l app.kubernetes.io/component=reports-controller

Les métriques incluent :

  • kyverno_policy_results_total — résultats de politiques
  • kyverno_policy_execution_duration_seconds — latence d’exécution

Grafana peut afficher les violations par namespace et politique.

Bonnes pratiques #

1. Commencer en Audit #

Passe toutes tes politiques en Audit pendant une semaine. Vérifie les violations dans kubectl get polr -A. Corrige les workloads non conformes avant de passer en Enforce.

2. Exclure les exceptions #

Certaines ressources système nécessitent des exceptions :

spec:
  validationFailureAction: Enforce
  exclude:
    - resources:
        namespaces:
          - kube-system
        kinds:
          - Pod
        names:
          - coredns-*

3. Versionner tes politiques #

Traite tes politiques comme du code : Git, pull requests, review. Kyverno supporte les ClusterPolicy versionnées :

metadata:
  annotations:
    policies.kyverno.io/title: Require Resources
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/description: >
      Cette politique oblige les conteneurs à avoir des resources limits.

4. Combiner avec Falco #

Kyverno et Trivy couvrent le build et l’admission. Falco surveille le runtime :

  • Trivy : “cette image a des vulnérabilités connues”
  • Kyverno : “ce déploiement ne respecte pas les politiques”
  • Falco : “ce conteneur fait quelque chose de suspect maintenant”

Trois couches, trois moments différents.

Conclusion #

Sécuriser un cluster Kubernetes, c’est plusieurs couches :

  1. Build — Trivy scanne les images avant de les push
  2. Admission — Kyverno valide les manifestes au déploiement
  3. Runtime — Falco (hors scope ici) détecte les comportements anormaux

Trivy et Kyverno couvrent les deux premières couches sans friction. Trivy s’intègre naturellement dans une CI, Kyverno s’intègre dans le contrôle plane Kubernetes. Les deux sont open source, maintenus activement, et la communauté fournit des politiques prêtes à l’emploi.

Le gain concret : tu bloques les déploiements non conformes, tu forces les équipes à utiliser des images patchées, et tu as de la visibilité sur les vulnérabilités. C’est pas parfait, mais c’est un excellent point de départ.