logo
< Back Post-Image

Gérer ses Policies sur Kubernetes avec Kyverno

Déjà mentionné dans notre article qui annonçait la dépréciation des PodSecurityPolicies et leur suppression à partir de la version 1.25, Kyverno s’annonce être un digne successeur à ces dernières.

Pour rappel, les PSP permettaient aux administrateurs de cluster de forcer l’application de bonnes pratiques en définissant un ensemble de règles qui étaient exécutées lors de la phase d’admission, au moyen d’un Admission Controller compiled-in.

Par exemple, les policies peuvent servir à:

  • Forcer la mise en place de labels afin d’harmoniser la gestion de vos ressources
  • Restreindre la surface d’attaque en interdisant de monter des hostPath
  • Empêcher un Pod d’utiliser des hostPort
  • Mettre en place des valeurs par défaut pour les limitations de ressource

Dans cet article, nous verrons comment mettre en place Kyverno, découvrir ses fonctionnalités et explorer ce que nous offre cette méthode de gestion des Policies.

Qu’est-ce que Kyverno ?

Kyverno est un moteur de gestion de Policies, permettant de définir des règles en tant que ressources Kubernetes. Une fois installé, Kyverno va analyser chaque ressource et appliquer les règles correspondantes.

Site: https://kyverno.io/

Afin d’avoir un comportement similaire, Kyverno intervient de la même façon que les PSP mais au moyen d’un Admission Controller dynamique qui traite des callbacks (webhooks d’admissions) de validation et de mutation en provenance de l’apiserver Kubernetes.

Les Policies Kyverno sont découpées en 3 parties:

  • Les règles, ayant chacune un selecteur et une action
  • Le selecteur de ressource de la règle, en inclusion ou en exclusion,
  • L’action de la règle, qui peut être une validation, une mutation ou une génération de ressource

Référence: Kyverno - Policy Structure.

Elles peuvent être appliquées au niveau d’un Namespace (Policy) ou du Cluster (ClusterPolicy). Ces deux ressources génèrent des PolicyReports (ou des ClusterPolicyReports) se basant sur le schéma proposé dans la Kubernetes Policy WG.

Mise en place de Kyverno

Installation

Commençons par mettre en place un environnement pour explorer les possibilités de Kyverno! En utilisant kind (présenté sur notre blog !), configurons un cluster local:

$ kind create cluster --name kyverno
Creating cluster "kyverno" ...
 ✓ Ensuring node image (kindest/node:v1.20.2) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-kyverno"
You can now use your cluster with:

kubectl cluster-info --context kind-kyverno

Thanks for using kind! 😊

Il existe différentes méthodes d’installation pour mettre en place Kyverno, utilisons le Chart Helm officiel pour bootstrapper notre cluster.

$ helm repo add kyverno https://kyverno.github.io/kyverno/
$ helm repo update
$ helm install kyverno kyverno/kyverno --namespace kyverno --create-namespace
NAME: kyverno
LAST DEPLOYED: Wed Feb 24 11:10:31 2021
NAMESPACE: kyverno
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Thank you for installing kyverno 😀

Your release is named kyverno.

We have installed the "default" profile of Pod Security Standards and set them in audit mode.

Visit https://kyverno.io/policies/ to find more sample policies.

Après quelques secondes, nous avons un cluster configuré avec un profile de sécurité par défaut en mode audit implémentant des règles basées sur les Pod Security Standards.

Bootstrapping

Nous pouvons lister les CRDs installés par le chart Helm:

$ kubectl api-resources --api-group kyverno.io
NAME                          SHORTNAMES   APIVERSION            NAMESPACED   KIND
clusterpolicies               cpol         kyverno.io/v1         false        ClusterPolicy
clusterreportchangerequests   crcr         kyverno.io/v1alpha1   false        ClusterReportChangeRequest
generaterequests              gr           kyverno.io/v1         true         GenerateRequest
policies                      pol          kyverno.io/v1         true         Policy
reportchangerequests          rcr          kyverno.io/v1alpha1   true         ReportChangeRequest
$ kubectl api-resources --api-group wgpolicyk8s.io
NAME                   SHORTNAMES   APIVERSION                NAMESPACED   KIND
clusterpolicyreports   cpolr        wgpolicyk8s.io/v1alpha1   false        ClusterPolicyReport
policyreports          polr         wgpolicyk8s.io/v1alpha1   true         PolicyReport

Les règles du profile “default” sont définies via des ClusterPolicies:

$ kubectl get cpol -n kyverno
NAME                             BACKGROUND   ACTION
disallow-add-capabilities        true         audit
disallow-host-namespaces         true         audit
disallow-host-path               true         audit
disallow-host-ports              true         audit
disallow-privileged-containers   true         audit
disallow-selinux                 true         audit
require-default-proc-mount       true         audit
restrict-apparmor-profiles       true         audit
restrict-sysctls                 true         audit

En effet, nous avons déjà un bon nombre de Policies ! Cependant, pas d’inquiétude. Nous pouvons observer qu’elles ont toutes été définies avec background=true et action=audit. Cette configuration indique à Kyverno que ces règles ne doivent pas être bloquantes. Toutes les 15 minutes, une analyse est lancée pour chaque règle sur les ressources présentes à cet instant au sein du Cluster et génèrent des ClusterPolicyReports.

Exemples d’utilisation

Création d’une ClusterPolicy

Il est temps de mettre en place notre première Policy ! Définissons une règle simple qui vérifie la présence d’un label identifiant le nom de l’application à laquelle un Pod correspond.

---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
spec:
  validationFailureAction: enforce
  rules:
    - name: check-for-label-name
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "label `app.kubernetes.io/name` is required"
        pattern:
          metadata:
            labels:
              app.kubernetes.io/name: "?*"

La règle check-for-label-name permet de considérer comme prérequis le label app.kubernetes.io/name pour le déploiement des ressources de type Pod.

Pour résumer cette règle:

Il est obligatoire pour tout Pod d’avoir un label app.kubernetes.io/name d’une longueur supérieure ou égale à 1 caractère.

Après avoir appliqué notre ClusterPolicy, nous obtenons la ressource suivante:

---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
  annotations:
    pod-policies.kyverno.io/autogen-controllers: DaemonSet,Deployment,Job,StatefulSet,CronJob
spec:
  background: true
  rules:
    - match:
      resources:
        kinds:
          - Pod
      name: check-for-label-name
      validate:
        message: label `app.kubernetes.io/name` is required
      pattern:
        metadata:
          labels:
            app.kubernetes.io/name: ?*
    - match:
      resources:
        kinds:
          - DaemonSet
          - Deployment
          - Job
          - StatefulSet
      name: autogen-check-for-label-name
      validate:
        message: label `app.kubernetes.io/name` is required
        pattern:
          spec:
            template:
              metadata:
                labels:
                  app.kubernetes.io/name: ?*
    - match:
      resources:
        kinds:
          - CronJob
      name: autogen-cronjob-check-for-label-name
      validate:
        message: label `app.kubernetes.io/name` is required
        pattern:
          spec:
            jobTemplate:
              spec:
                template:
                  metadata:
                    labels:
                      app.kubernetes.io/name: ?*
      validationFailureAction: enforce

On retrouve notre règle “check-for-label-name”, mais deux autres règles ont été autogénérées afin d’appliquer le comportement que nous souhaitons aux autres ressources permettant de définir des pods.

Ces règles sont créées par les autogen-controllers (link) référencés par l’annotation correspondante qui sert d’annotation par défaut. Définir manuellement cette annotation permet de modifier le comportement de génération automatique de règles.

Essayons de déployer une ressource qui enfreint la règle que nous venons de créer:

$ kubectl run sample --image=busybox
Error from server: admission webhook "validate.kyverno.svc" denied the request:

resource Pod/default/sample was blocked due to the following policies

require-labels:
  check-for-labels: 'validation error: label `app.kubernetes.io/name` is required. Rule check-for-label-name failed at path /metadata/labels/app.kubernetes.io/name/'
$ kubectl run sample --image=busybox  --labels app.kubernetes.io/name=valid
pod/sample created

Notre Policy a une validationFailureAction=enforce, il nous est donc impossible de créer une ressource ne validant pas l’intégralité des règles définies!

Après avoir ajouté un label pour le nom de notre Pod, le webhook d’admission Kyverno valide la requête et la ressource est créée.

Le mode “audit”

Lors du déploiement du Chart Helm, un profil par défaut a été configuré en mode audit.

We have installed the “default” profile of Pod Security Standards and set them in audit mode.

Ce mode de validation de Policy est non-intrusif et permet de mettre progressivement en application les règles. Il n’empêche pas la création des ressources qui ne sont pas conformes à la Policy mais va historiser les infractions dans des PolicyReport (polr) si la ressource est namespaced (comme un Deployment ou un ConfigMap) dans le namespace de la ressource, ou des ClusterPolicyReports (cpolr) s’il s’agit de ressource non namespaced (un Namespace)

Lors de la création d’un Deployment, le template du Pod enfreint une ClusterPolicy. L’infraction sera reportée dans une PolicyReport dans le namespace du Pod.

Lors de la création d’une IngressClass, l’absence de label enfreint une autre ClusterPolicy. L’infraction sera reportée dans une ClusterPolicyReport.

Comme mentionné précédemment, le mode audit va périodiquement (toutes les 15 minutes) analyser les ressources et générer des reports.

Configurons une Policy en mode audit ainsi qu’un Pod de test:

---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-tier-on-pod
spec:
  validationFailureAction: audit
  background: true
  rules:
    - name: check-for-tier-on-pod
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "if set, label `app.kubernetes.io/tier` must be any of ['frontend', 'backend', 'internal']"
        pattern:
          metadata:
            labels:
              =(app.kubernetes.io/tier): "frontend | backend | internal"
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    app.kubernetes.io/tier: uknown
    app.kubernetes.io/name: example-pod
  name: bad-tier
spec:
  containers:
    - image: nginx:latest
      name: example-tier

Si le label app.kubernetes.io/tier existe, il doit correspondre à l’une des valeurs frontend, backend ou internal.

Après quelques instants, nous pouvons observer un avertissement venant de l’admission-controller:

$ kubectl describe pod bad-tier  | grep -A 5 Events
Events:
  Type     Reason           Age   From                  Message
  ----     ------           ----  ----                  -------
  Warning  PolicyViolation  8s   admission-controller  Rule(s) 'check-for-tier-on-pod' of policy 'require-tier-on-pod' failed to apply on the resource
  Normal   Scheduled        8s   default-scheduler     Successfully assigned default/bad-tier to kyverno-control-plane
  Normal   Pulling          7s   kubelet               Pulling image "nginx:latest"
$ kubectl get polr
NAME              PASS   FAIL   WARN   ERROR   SKIP   AGE
polr-ns-default   1      1      0      0       0      30m

Nous pouvons obtenir un report complet en effectuant un describe sur le PolicyReport du Namespace correspondant.

---
apiVersion: wgpolicyk8s.io/v1alpha1
kind: PolicyReport
metadata:
  name: polr-ns-default
  namespace: default
results:
  - message: 'validation error: if set, label `app.kubernetes.io/tier` must be any of
    [''frontend'', ''backend'', ''internal'']. Rule check-for-tier-on-pod failed at
    path /metadata/labels/app.kubernetes.io/tier/'
    policy: require-tier-on-pod
    resources:
      - apiVersion: v1
        kind: Pod
        name: bad-tier
        namespace: default
        uid: 6e743322-5b2a-4ad7-bc79-eca437ab82db
    rule: check-for-tier-on-pod
    scored: true
    status: fail
  - category: Pod Security Standards (Default)
    message: validation rule 'host-namespaces' passed.
    policy: disallow-host-namespaces
    resources:
      - apiVersion: v1
        kind: Pod
        name: sample
        namespace: default
        uid: 19dc0c5b-3960-4178-8bb0-6cd61a23c089
    rule: host-namespaces
    scored: true
    status: pass
summary:
  error: 0
  fail: 1
  pass: 1
  skip: 0
  warn: 0

Les PolicyReports permettent d’avoir une vue d’ensemble des infractions locales au namespace dans lequel elles résident.

Chaque report inclut un récapitulatif de l’application des règles avec le nombre d’infraction, de validation, d’avertissements, …

Comment implémenter Kyverno

Lors de la mise en place d’un Cluster

Afin de partir sur de bonnes bases, vous pouvez intégrer Kyverno à votre Cluster Kubernetes après sa création en surchargeant les valeurs par défaut du Chart Helm.

Une version plus sécurisée du profile “default” est disponible via le profile “restricted” et peut être accompagnée d’un validationFailureAction=enforce afin de garantir l’intégrité et la sécurité du cluster.

Dans un Cluster existant

Il est totalement possible d’ajouter Kyverno à une configuration en utilisant le même procédé. Attention cependant à ne pas enforce les Policies que vous mettez en place de façon à ne pas cause d’interruption.

Une transition vers des ressources conformes aux règles que vous souhaitez définir pourrait s’effectuer de en utilisant la méthode suivante:

  • Définition des règles à implémenter via Kyverno en mode audit
  • Observer les infractions via les PolicyReports et les ClusterPolicyReports
  • Ajout d’une étape de validation dans les CI/CD via Kyverno CLI
  • Passage en mode “enforce” lorsque les ressources sont conformes à la Policy

Conclusion

La mise en place d’une solution de gestion de Policy est une étape importante concernant la sécurisation des clusters Kubernetes.

Kyverno réussit à répondre à cette problématique d’une manière élégante et peut se révéler être un excellent remplaçant aux PSP. La dimension Kubernetes Native rend la courbe d’apprentissage très douce, ne nécessitant pas d’apprendre de nouvelle syntaxe.

Dans cet article, nous avons couvert les similarités au niveau des PSP. Cependant, Kyverno propose des fonctionnalités additionnelles, telle que la mutation de ressources permettant de rajouter des valeurs par défauts, ou encore la génération de ressources qui offre la possibilité de répliquer des ressources à travers différents namespaces ou d’automatiser certaines opérations.

Theo “Bob” Massard, Cloud Native Engineer