ArgoCD excelle pour synchroniser vos applications depuis Git. Mais il ne vérifie pas que ce qu'il déploie respecte vos politiques de sécurité. C'est le rôle d'un admission controller comme Kyverno ou Gatekeeper. Ce guide montre comment les déployer ensemble, dans une architecture GitOps cohérente.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Déployer Kyverno via ArgoCD en pattern App of Apps
- Utiliser les sync-waves pour ordonner CRDs → opérateur → politiques
- Configurer ServerSideApply, ServerSideDiff et IncludeMutationWebhook pour éviter les faux OutOfSync
- Vérifier les images signées (Cosign) et la provenance (SLSA) à l'admission
- Passer progressivement de Audit à Enforce
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Vous avez mis en place ArgoCD et vos applications sont déployées en GitOps. Mais plusieurs situations courantes restent ouvertes :
- Un développeur pousse une image Docker non signée, elle finit en production
- Un Deployment tourne avec
runAsNonRoot: false, personne ne le bloque - Un Service expose un
externalIP, porte ouverte à un MITM (CVE-2020-8554) - Vous signez vos images avec Cosign, mais rien ne vérifie la signature au moment du déploiement
Un admission controller comble ces lacunes. Combiné à ArgoCD, il forme le dernier maillon d'une chaîne de supply chain complète.
Architecture cible
Section intitulée « Architecture cible »config-repo/ infra-services/ # ← app racine ArgoCD (sync-wave 0) kyverno.yaml # Application: déploie Kyverno (wave 1) kyverno-policies.yaml # Application: déploie les policies (wave 2) kyverno/ Chart.yaml # wrapper Helm → kyverno 3.7.1 values.yaml kyverno-policies/ Chart.yaml # wrapper Helm → kyverno-policies values.yaml templates/ # vos policies custom (ValidatingPolicy ou ClusterPolicy legacy) restrict-external-ips.yaml require-image-signature.yamlLe pattern App of Apps garantit que tout est piloté depuis Git. Les sync-waves garantissent l'ordre.
Étape 1 : déployer Kyverno via ArgoCD
Section intitulée « Étape 1 : déployer Kyverno via ArgoCD »-
Créer le wrapper Helm
kyverno/Chart.yaml apiVersion: v2name: kyvernodescription: Wrapper Helm pour Kyvernotype: applicationversion: 1.0.0dependencies:- name: kyvernoversion: "3.7.1"repository: https://kyverno.github.io/kyverno/kyverno/values.yaml kyverno:config:preserve: false # évite les OutOfSync liés au hook Helm de préservationcleanupController:rbac:clusterRole:extraResources:- apiGroups: [""]resources: [pods]verbs: [get, list, watch, delete] -
Créer l'Application ArgoCD avec les bonnes options
infra-services/kyverno.yaml apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata:name: kyvernonamespace: argocdannotations:argocd.argoproj.io/sync-wave: "1"argocd.argoproj.io/compare-options: >-ServerSideDiff=true,IncludeMutationWebhook=truespec:project: infrasource:repoURL: https://github.com/votre-org/config-repotargetRevision: mainpath: kyvernohelm:valueFiles:- values.yamldestination:server: https://kubernetes.default.svcnamespace: kyvernosyncPolicy:automated:prune: trueselfHeal: truesyncOptions:- CreateNamespace=true- ServerSideApply=trueTrois points critiques :
Option Pourquoi ServerSideApply=trueLes CRDs Kyverno dépassent 262 Ko, le client-side apply échoue ServerSideDiff=trueKyverno mute des ressources, sans ça, ArgoCD voit du drift permanent. Stable depuis ArgoCD v3.1.0, ce mode fait participer les admission controllers au calcul du diff. IncludeMutationWebhook=trueIntègre les mutations de Kyverno dans le calcul des diffs Si vous utilisez largement les mutations Kyverno, envisagez d'activer
ServerSideDiffglobalement dans la configuration ArgoCD plutôt que de le répéter Application par Application. Kyverno le recommande explicitement dans ses notes plateforme. -
Créer l'Application pour les politiques (wave 2)
infra-services/kyverno-policies.yaml apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata:name: kyverno-policiesnamespace: argocdannotations:argocd.argoproj.io/sync-wave: "2"spec:project: infrasource:repoURL: https://github.com/votre-org/config-repotargetRevision: mainpath: kyverno-policieshelm:valueFiles:- values.yamldestination:server: https://kubernetes.default.svcnamespace: kyvernosyncPolicy:automated:prune: trueselfHeal: truesyncOptions:- ServerSideApply=trueLe
sync-wave: "2"garantit que Kyverno est installé et Healthy avant que les politiques ne soient créées.
Une note de resource tracking : ArgoCD et le chart Helm Kyverno utilisaient tous deux le label app.kubernetes.io/instance, source de conflits en 2.x. Depuis ArgoCD 3.0, le suivi se fait par annotation par défaut, ce qui règle ce conflit sans configuration. Si vous êtes resté sur le suivi par label, définissez webhookLabels dans les values Kyverno pour distinguer les webhooks dans l'interface (voir les notes plateforme Kyverno).
Étape 2 : premières politiques
Section intitulée « Étape 2 : premières politiques »Interdire les externalIPs (CVE-2020-8554)
Section intitulée « Interdire les externalIPs (CVE-2020-8554) »CVE-2020-8554 est une vulnérabilité de conception dans Kubernetes qui permet
un MITM via les services externalIPs. Il n'y a pas de correctif natif, la
mitigation passe par une politique d'admission.
apiVersion: policies.kyverno.io/v1kind: ValidatingPolicymetadata: name: restrict-external-ipsspec: validationActions: [Audit] # passer en [Deny] après validation matchConstraints: resourceRules: - apiGroups: [""] apiVersions: ["v1"] operations: ["CREATE", "UPDATE"] resources: ["services"] validations: - expression: "!has(object.spec.externalIPs) || size(object.spec.externalIPs) == 0" message: "externalIPs are not allowed (CVE-2020-8554)."Exiger des images signées avec Cosign
Section intitulée « Exiger des images signées avec Cosign »La capacité VerifyImages de Kyverno vérifie les signatures Cosign et les attestations de provenance SLSA avant admission.
apiVersion: policies.kyverno.io/v1kind: ImageValidatingPolicymetadata: name: require-signed-imagesspec: validationActions: [Audit] # passer en [Deny] après validation matchConstraints: resourceRules: - apiGroups: [""] apiVersions: ["v1"] operations: ["CREATE", "UPDATE"] resources: ["pods"] matchImageReferences: - glob: "registry.example.com/*" attestors: - name: cosign-keyless cosign: keyless: identities: - subjectRegExp: "^https://github.com/votre-org/" issuer: "https://token.actions.githubusercontent.com" validations: - expression: "true" message: "Image must be signed with a valid Cosign keyless signature."Avant de multiplier les politiques, comprenez une subtilité de portée :
écrire une règle sur les Pod ne signifie pas qu'elle ignore vos
Deployments et StatefulSets. Kyverno étend automatiquement la règle aux
contrôleurs de pods qui les créent, ce qui change ce que vous verrez dans les
PolicyReport.
Interdire les conteneurs root
Section intitulée « Interdire les conteneurs root »apiVersion: policies.kyverno.io/v1kind: ValidatingPolicymetadata: name: require-run-as-nonrootspec: validationActions: [Audit] # passer en [Deny] après validation matchConstraints: resourceRules: - apiGroups: [""] apiVersions: ["v1"] operations: ["CREATE", "UPDATE"] resources: ["pods"] matchConditions: - name: exclude-system-ns expression: >- !(['kube-system','kyverno'].exists(ns, object.metadata.namespace == ns)) validations: - expression: >- object.spec.containers.all(c, has(c.securityContext) && has(c.securityContext.runAsNonRoot) && c.securityContext.runAsNonRoot == true ) message: "Les conteneurs doivent tourner en non-root (runAsNonRoot: true)."Étape 3 : vérifier le fonctionnement
Section intitulée « Étape 3 : vérifier le fonctionnement »Une fois les politiques déployées, vérifiez que Kyverno fonctionne :
# Lister les policieskubectl get validatingpolicies.policies.kyverno.iokubectl get imagevalidatingpolicies.policies.kyverno.io
# Vérifier les PolicyReport (violations en mode Audit)kubectl get policyreport -A
# Tester une violationkubectl run test-root --image=nginx --restart=Never \ --overrides='{"spec":{"containers":[{"name":"test","image":"nginx","securityContext":{"runAsNonRoot":false}}]}}'
# Voir la violationkubectl get policyreport -A -o wideEn mode [Audit], le pod est créé mais la violation est enregistrée. En mode
[Deny], la création est bloquée.
Étape 4 : passage Audit → Enforce
Section intitulée « Étape 4 : passage Audit → Enforce »Le rollout progressif se fait entièrement via Git :
-
Mesurer : consultez les
PolicyReportpour identifier les workloads non conformesFenêtre de terminal kubectl get policyreport -A --no-headers | wc -l -
Corriger : adaptez les Deployments et StatefulSets concernés (ajoutez
securityContext, retirez lesexternalIPs...) -
Basculer : modifiez
validationActions: [Deny]dans les fichiers YAML, commitez, poussez, ArgoCD réconcilie -
Surveiller : vérifiez que les applications restent Healthy dans l'interface ArgoCD
Kyverno vs Gatekeeper : quel admission controller avec ArgoCD ?
Section intitulée « Kyverno vs Gatekeeper : quel admission controller avec ArgoCD ? »| Critère | Kyverno | Gatekeeper (OPA) |
|---|---|---|
| Langage des politiques | YAML Kubernetes natif + CEL | Rego + CRDs Gatekeeper |
| Courbe d'apprentissage | Plus faible, même syntaxe que vos manifestes | Plus forte, Rego est un langage déclaratif différent |
| Vérification d'images | VerifyImages natif (Cosign, Notary) | Pas natif, nécessite un composant externe |
| Mutate | Oui, natif | Oui (stable depuis v3.10+ : Assign, AssignMetadata, ModifySet, AssignImage) |
| Generate | Oui, natif | Non |
| Cleanup | Oui, natif | Non |
| Maturité CNCF | Graduated (mars 2026) | Graduated |
| Intégration ArgoCD | Nécessite ServerSideDiff + IncludeMutationWebhook | Nécessite ServerSideDiff |
Pour un usage supply chain (vérification d'images, provenance, mutations), Kyverno est le choix le plus naturel. Pour un comparatif détaillé, voir VAP vs Kyverno vs Gatekeeper.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause | Solution |
|---|---|---|
no matches for kind "ValidatingPolicy" | CRDs pas encore installés | Vérifier le sync-wave, les policies doivent être en wave > Kyverno |
| OutOfSync permanent sur Kyverno | Mutations non prises en compte dans le diff | Ajouter ServerSideDiff=true,IncludeMutationWebhook=true |
Erreur request entity too large | CRDs > 262 Ko | Activer ServerSideApply=true dans syncOptions |
| Pods bloqués après passage en Deny | Policy trop restrictive | Repasser en [Audit], corriger les manifestes, puis [Deny] |
| ArgoCD lui-même bloqué par Kyverno | Policy trop large (match *) | Exclure le namespace argocd dans la policy |
| OutOfSync sur Kyverno après upgrade | Hook Helm de préservation actif | Définir config.preserve: false dans les values Kyverno |
Conflit label app.kubernetes.io/instance (installations restées en 2.x) | ArgoCD et Kyverno utilisaient le même label | Réglé par défaut en 3.x (tracking par annotation) ; sur une install 2.x, basculer en annotation ou ajuster webhookLabels |
| Rapports Kyverno absents de l'interface ArgoCD | Ressources Kyverno exclues par défaut depuis ArgoCD 3.0 | Comportement normal (moins de bruit) ; pour les afficher, retirer le kind de resource.exclusions dans argocd-cm |
À retenir
Section intitulée « À retenir »- Kyverno + ArgoCD = policy-as-code GitOps, vos politiques sont versionnées, revues et déployées comme du code applicatif
- Les sync-waves sont indispensables : CRDs (wave 1) → policies (wave 2)
- ServerSideApply est obligatoire pour les CRDs volumineux de Kyverno
- ServerSideDiff + IncludeMutationWebhook évitent les faux OutOfSync
- VerifyImages permet de vérifier les signatures Cosign et la provenance SLSA directement à l'admission Kubernetes
- Commencez en Audit, mesurez, corrigez, puis passez en Deny
- Excluez toujours les namespaces
argocdetkyvernodes policies Enforce
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Kyverno, Policy Engine Kubernetes : Guide complet Kyverno : politiques YAML, validate, mutate, generate, verifyImages et CEL.
- Admission Controllers Kubernetes : Comprendre les admission controllers et leur rôle dans la sécurité du cluster.
- Cosign, signer vos images : Signature keyless, vérification et intégration CI/CD.
- Provenance SLSA avec Kyverno : Vérifier la provenance SLSA en CI et au déploiement Kubernetes.