Aller au contenu
kyvernoDevOpsGitlabcicdhelmSécuritéCloud

Kyverno : un allié insoupçonné pour tester vos charts Helm

Si vous avez adopté Kyverno pour gérer la conformité de vos clusters Kubernetes, découvrez comment gagner du temps en réutilisant cette techno pour réaliser les tests unitaires de vos charts !

Kyverno accélère vos tests Helm

Kyverno n'est certainement pas la première solution qui vient à l'esprit du DevOps averti lorsqu'il est question de tests unitaires et de charts Helm.
Certains outils comme helm-unittest ou conftest (si vous aimez le Rego 😄) sont souvent choisis d'office pour répondre à ce besoin. Quelle que soit leur degré de popularité, tous deux imposent de passer par une une courbe d'apprentissage afin d'être maîtrisés.

Bien que la découverte de nouvelles technologies fasse partie intégrante de notre métier, il peut parfois être intéressant de faire preuve de pragmatisme pour gagner en productivité ⚡. Ainsi, réutiliser une solution déjà en place représente souvent un gain de temps appréciable et facilite l'adoption au sein des équipes.

Le principe

Pour réaliser notre premier test, il faut commencer par construire le chart cible. Cela peut être fait avec les valeurs par défaut du chart ou bien en utilisant des valeurs spécifiques à un scénario souhaité.

helm template charts/my-awesome-chart --values=policies/my-awesome-chart/basic/values.yaml

Il est ensuite possible de stocker ou de rediriger le yaml résultant de la commande précédente pour le rediriger vers une commande kyverno. Cette dernière évaluera la conformité de notre résultat vis-à-vis d'une politique choisie :

helm template charts/my-awesome-chart --values=policies/my-awesome-chart/basic/values.yaml | kyverno apply --resource - policies/my-awesome-chart/basic/policy.yaml

En fonction du contenu de la politique ainsi que du template, un résultat de ce type est obtenu :

Applying 3 policy rules to 4 resources...

pass: 2, fail: 0, warn: 0, error: 0, skip: 10

ℹ️ Exemple de disposition pour un dépôt dédié à l'écriture et au test de charts Helm avec Kyverno

Comment rédiger une politique orientée test unitaire ?

Au risque de décevoir, la réponse à cette question est simple : de la même manière que n'importe quelle autre politique !
Et c'est bien là que réside tout l'intérêt de réutiliser Kyverno dans ce contexte.

Pour donner quelques pistes, voici deux exemples de tests qui peuvent être transformés pour s'adapter à vos besoins.

Test n°1

Ce premier test valide la présence des trois IPs listées dans une ressource de type Middleware (Traefik) nommée ipwhitelist.

Sont ici combinés :

  • Une règle de validation de type deny 🚫
  • L'opérateur any entraînant le déclenchement de la règle deny dès qu'une condition est vérifiée
  • Trois conditions recherchant la présence d'une IP dans une liste

Si jamais une ou plusieurs des trois IPs s'avèrent absentes, alors la condition any est validée et le test échoue ❌.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: validate-ipwhitelist-middleware
  annotations:
    policies.kyverno.io/title: Validate IP Whitelist Middleware
    policies.kyverno.io/category: Security
    policies.kyverno.io/severity: medium
    policies.kyverno.io/subject: Middleware
    policies.kyverno.io/description: >-
      Validates that IP whitelist middleware contains the correct IP ranges
spec:
  validationFailureAction: audit
  background: true
  rules:
	- name: validate-ipwhitelist
	  match:
		resources:
		  kinds:
			- Middleware
	  validate:
		message: "IP whitelist middleware must contain the correct IP ranges"
		deny:
		  conditions:
			any:
			- key: "{{ contains(request.object.spec.ipWhiteList.sourceRange, '10.10.0.0/16') }}"
			  operator: Equals
			  value: false
			- key: "{{ contains(request.object.spec.ipWhiteList.sourceRange, '192.168.0.0/24') }}"
			  operator: Equals
			  value: false
			- key: "{{ contains(request.object.spec.ipWhiteList.sourceRange, '8.8.8.8/32') }}"

Test n°2

Plus simple, ce second test se compose de :

  • Une règle de validation de type pattern 
  • Un opérateur de négation X() vérifiant qu'un champ n'est pas défini

Tant que le champ secretObjects reste absent de .spec.SecretProviderClass, la condition de négation est respectée et le test passe ✅.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: validate-standard-app
  annotations:
    policies.kyverno.io/title: Standard SecretProviderClass
    policies.kyverno.io/category: Best Practices
    policies.kyverno.io/severity: medium
    policies.kyverno.io/subject: SecretProviderClass
    policies.kyverno.io/description: >-
      SecretProviderClass objects must have specific attributes.
spec:
  validationFailureAction: audit
  background: true
  rules:
    - name: check-for-null-secretobjects
      match:
        resources:
          kinds:
            - SecretProviderClass
	      name: "myapp"  
      validate:
        message: "The SecretProviderClass secretObjects should not be defined (empty secrets list)."
        pattern:
          spec:
            X(secretObjects): "null"

Et l'automisation dans tout ça ?

Évidemment, tout cela n'a que peu d'intérêt sans un processus d'intégration continue pour automatiser l'exécution de ces tests ⚙️.

Cette proposition de CI GitLab s'occupe de parcourir l'arborescence de fichiers présentée précédemment et d'exécuter tous les tests associés dès qu'un chart est modifié.

⚠️ Attention : il est nécessaire de disposer d'une image Docker qui inclut les binaires kyverno-cli et helm.

stages:
  - kyverno

.charts:
  parallel:
    matrix:
      - CHART:
          - my-awesome-chart
          - my-second-awesome-chart

.rule_chart_modified:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      changes:
        - charts/$CHART/**/*
        - policies/$CHART/**/*
      when: on_success

kyverno:
  tags:
    - my-runner
  stage: kyverno
  image:
    name: helm-kyverno:latest # /!\ Necessite une image embarquant helm et kyverno-cli
  script:
    - |
      echo ##### "Processing chart: ${CHART}" #####
      
      # Mise a jour des dependances Helm
      cd charts/${CHART}
      helm dependency update 2>/dev/null || helm dependency build
      
      # Traitement de chaque fichier values.yaml
      find ../../policies/${CHART} -name values.yaml | while read -r values_file; do
        policy_dir=$(dirname "$values_file")
        policy_name=$(basename "$policy_dir")
        
        echo "## Processing policy: ${policy_name} ##"
        
        # Generation du manifest et lancement du test kyverno
        helm template . --values="$values_file" | kyverno apply --resource - "$policy_dir"
        
        echo -e "########\n\n"
      done
  extends:
    - .charts
    - .rule_chart_modified

Proposition de CI pour l'automatisation des tests Kyverno

A vous de jouer !

L'avantage principal de cette approche est maintenant clair : un seul outil, moins de complexité, plus d'efficacité.

Vous disposez désormais d'une bonne base pour l'implémenter dans votre processus de développement de charts et si vous utilisez déjà Kyverno en production, vous constaterez rapidement que certaines de vos politiques existantes peuvent être réutilisées sans modification pour vos tests ! 🔄

Il était une fois... Kyverno
Conçu pour simplifier la gestion des politiques de sécurité et de conformité, Kyverno est aujourd’hui l’un des outils de gouvernance de politiques les plus populaires dans l’écosystème Kubernetes. Installez-vous, je vais vous raconter cette histoire.

Dernier