Découverte de l’outil#
J’ai eu l’occasion lors de la KubeCon Valence de 2022 de rencontrer l’équipe de l’entreprise Nirmata en charge de l’outil Kyverno.
C’était un outil que je connaissais déjà sans jamais l’avoir mis en place. J’ai donc voulu en savoir plus, et, tester Kyverno pour une utilisation personnelle et vous permettre de faire la même chose à travers cet article.
Kyverno est un projet open source au niveau incubating côté CNCF, il permet de déployer des stratégies (Policies
) visant, entre autres, à valider les déploiements au sein de clusters Kubernetes et sécuriser les ressources en appliquant les bonnes pratiques.
Par exemple, il peut empêcher la création de ressources sans label, de Pod
possédant une image sans tag, etc.
De plus, il dispose d’une large communauté de contributeurs à travers l’ensemble des dépôts de code sur Github.
Dans la suite de cet article, je vais vous présenter Kyverno, l’installer et vous faire découvrir quelques fonctionnalités.
Pourquoi choisir Kyverno ?#
Lors de la certification CKS, j’avais eu l’occasion de manipuler les Pod Security Policies et l’outil open source Open Policy Agent (OPA).
Pour ce qui concerne le premier élément, l’objet PodSecurityPolicy
a été déprécié depuis la version 1.21 de Kubernetes et a été totalement supprimé depuis la version 1.25. Celui-ci permettait de fixer des restrictions au niveau des Pod
comme le fait d’éviter d’accorder des privilèges trop élevés à un conteneur. Enfin, son successeur le Pod Security Admission est encore trop récent.
Ensuite, pour OPA, cet outil permet, comme Kyverno, de créer des stratégies au sein d’un cluster Kubernetes en mode Policy as Code (PaC).
L’Open Policy Agent a l’avantage d’être une solution généraliste qui ne se limite pas uniquement à Kubernetes, même si une solution spécifique existe comme Gatekeeper.
Là où j’ai trouvé OPA moins pratique c’est à travers le langage Rego, qui est un langage très particulier et pas forcément simple à appréhender. C’est la différence majeure avec Kyverno qui se base sur des fichiers YAML en adoptant la forme d’objets natifs à Kubernetes car il a été pensé uniquement pour fonctionner sur cet orchestrateur.
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}
Voici un exemple de Rego pour valider l’utilisation de labels. Pas si simple…
En conclusion, je trouve que Kyverno est donc plus accessible pour une personne habituée à manipuler des objets Kubernetes sans avoir à apprendre un nouveau langage.
Architecture et fonctionnement#
Pour ce qui est du fonctionnement de l’outil, Kyverno va opérer au niveau des parties Mutating admission (modification d’objets) et Validating admission (validation d’objet) de la requête HTTP envoyée au kube-apiserver dans le but de modifier l’objet demandé, d’autoriser ou non sa création dans l’orchestrateur.
C’est pour cela qu’il s’exécute comme un Dynamic Admission Controller.
En plus de cela, Kyverno va aussi reporter les ressources qui ne correspondent pas aux stratégies définies (Policy) à travers des rapports ou dans les événements du cluster.
La documentation officielle de Kyverno met à disposition un schéma pour expliquer le fonctionnement de l’outil et les différents éléments qui composent son architecture :
- Le
PolicyController
se charge d’analyser les stratégies mises en place au sein de Kyverno et de faire de manière continue des scans ; - Le
GenerateController
s’occupe du cycle de vie des ressources générées via l’instructiongenerate
que l’on verra plus tard ; - Enfin, le
Webhook
est en interaction avec les requêtes dukube-apiserver
qu’elles proviennent des phases du Mutating admission ou Validating admission. LeMonitor
crée et gère les configurations.
Une fois Kyverno installé, vous serez en mesure de créer des stratégies (Policy) et différentes règles au sein de votre cluster.
Deux types d’objet pour les stratégies sont disponibles :
ClusterPolicy
pour définir des règles sur l’ensemble du cluster ;Policy
pour définir des règles sur unnamespace
en particulier.
Chaque stratégie se compose d’une ou plusieurs règles.
Ces règles peuvent contenir des règles de correspondance match avec la possibilité d’exclure des ressources et peuvent se baser sur plusieurs caractéristiques :
kind
, qui représente le type de la ressource ;name
, pour le nom de la ressource ;label
, pour le ou les paires clé-valeur associées à la ressource ;namespace
, pour l’espace de nom au sein du cluster ;role
, qui correspond à un rôle associé à un utilisateur ;group
, pour un groupe d’utilisateurs ;- et enfin, il est possible aussi d’associer un nom d’utilisateur
user
.
Enfin, chaque règle peut effectuer une seule action :
validate
, dans le but de valider une ou plusieurs ressources en imposant que celles-ci disposent de labels par exemple ;mutate
pour modifier une ou plusieurs ressources en utilisant des patchs au format JSONPatch - RFC 6902 ;generate
pour créer une ou plusieurs ressources additionnelles ;verifyImages
qui utilise le projet open source cosign pour vérifier la signature d’images.
Kyverno dispose d’une fonctionnalité pratique dans le cas d’une mise en place d’un panel de stratégies sur un cluster Kubernetes existant afin de ne pas impacter le fonctionnement de celui-ci.
En effet, il est possible de spécifier le paramètre validationFailureAction
en audit
plutôt qu’en enforce
ce qui permet de ne pas bloquer la création de ressources mais de remonter l’information dans les logs.
Dernier point intéressant, Kyverno possède une base de données avec des Policies
déjà prêtes à l’emploi, ce qui évite de réinventer la roue et permet de se baser sur des stratégies déjà existantes.
Installation et découverte de l’outil#
Passons maintenant à l’installation de Kyverno, avant toute chose, il est recommandé de regarder la compatibilité de chaque version de l’outil avec la version du cluster Kubernetes où Kyverno sera installé.
Dans la suite, j’ai choisi la version v1.8.0
qui est la dernière version mise à disposition lors de l’écriture de cet article et mon cluster est en version v1.25.3
.
La manière la plus commune est d’installer Kyverno en utilisant le chart Helm officiel, néanmoins, plusieurs modes d’installation sont disponibles également.
# Ajoute le repository Kyverno à Helm
helm repo add kyverno https://kyverno.github.io/kyverno/
# Permet de mettre à jour l'ensemble des repo
helm repo update
# Crée le namespace Kyverno
kubectl create ns kyverno
# Installe Kyverno dans le namespace du même nom en mode haute disponibilité
helm install kyverno kyverno/kyverno --version 2.6.0 --namespace kyverno --set replicaCount=3
La commande ci-dessous permet de vérifier que Kyverno est correctement déployé :
$ kubectl -n kyverno get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
kyverno 3/3 3 3 108s
$ kubectl -n kyverno get pods
NAME READY STATUS RESTARTS AGE
kyverno-5f7d9f9675-672lf 1/1 Running 0 75s
kyverno-5f7d9f9675-7lwn2 1/1 Running 0 75s
kyverno-5f7d9f9675-cr5r6 1/1 Running 0 75s
Les Pod
sont en statut Running et sont Ready, tout semble bon.
Stratégie de validation#
Il est maintenant temps de déployer notre première ClusterPolicy
, qui sera assez simple à comprendre avec pour objectif de vérifier la présence d’un tag au niveau de l’image du conteneur. La version brute de cette stratégie se trouve ici.
Cette stratégie se compose de deux règles :
- La première permet de vérifier qu’un tag est bien associé à l’image d’un
Pod
; - La seconde rejette le tag
:latest
d’une image.
On retrouve dans ces deux règles une structure identique :
- La partie
match
pour appliquer la règle sur une ou plusieurs ressources spécifiques, dans ce cas de figure, on est au niveau duPod
. - La partie
validate
pour vérifier selon un modèle (pattern
) la structure duPod
. Dans cette situation, on regarde le champimage
et son format.
Pour exécuter notre stratégie, il est nécessaire d’exécuter cette ligne de commande :
kubectl create -f https://raw.githubusercontent.com/kyverno/policies/main/best-practices/disallow_latest_tag/disallow_latest_tag.yaml
Petit rappel, une ClusterPolicy
n’a pas de namespace
en particulier, elle est active sur l’ensemble du cluster.
Maintenant, pour tester, on peut créer un simple Pod
avec comme image nginx
sans tag particulier :
kubectl run nginx --image=nginx
La ClusterPolicy
créée est en mode audit
, c’est pourquoi la création du Pod
n’a pas été bloquée. Cependant, on peut récupérer le rapport qui est un objet de type policyreports
:
$ kubectl get policyreports
NAME PASS FAIL WARN ERROR SKIP AGE
cpol-disallow-latest-tag 1 1 0 0 0 2m30s
$ kubectl get policyreports cpol-disallow-latest-tag -o yaml | grep "result: fail" -B10
- category: Best Practices
message: 'validation error: An image tag is required. rule require-image-tag failed
at path /spec/containers/0/image/'
policy: disallow-latest-tag
resources:
- apiVersion: v1
kind: Pod
name: nginx
namespace: default
uid: f2f2cba4-7bc2-4456-b206-9609c00e4154
result: fail
Pour mettre à jour la Policy
en mode enforce
, cette commande va mettre à jour notre stratégie :
kubectl patch clusterpolicy disallow-latest-tag --type merge --patch '{"spec": {"validationFailureAction": "enforce"}}'
Maintenant, si on retente la création du Pod
, on tombe sur cette erreur assez parlante :
$ kubectl run nginx --image=nginx
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
policy Pod/default/nginx for resource violation:
disallow-latest-tag:
require-image-tag: 'validation error: An image tag is required. rule require-image-tag
failed at path /spec/containers/0/image/'
Et si on fixe le tag, pas d’erreur et le Pod
est créé :
$ kubectl run nginx-tag --image=nginx:1.23.2
pod/nginx-tag created
$ kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-tag 1/1 Running 0 10s
Stratégie de mutation#
Après avoir testé la validation au sein de Kyverno, les stratégies de mutation permettent d’effectuer des changements sur des ressources qui souhaitent être créées. Les mutations peuvent ajouter, modifier ou supprimer du contenu.
Un point important à prendre en compte, c’est que les mutations sont effectuées avant la validation. Il ne faut donc pas que la règle de validation vienne contredire le changement appliqué par la stratégie de mutation.
L’exemple le plus simple et rapide à mettre en place est de se baser sur cette ClusterPolicy
avec le format brut via ce lien.
Quelques modifications sont à réaliser pour en faire une Policy
qui ne s’appliquera qu’au namespace
app-backend
en appliquant plusieurs labels
recommandés :
cat <<EOF | kubectl create -f -
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: add-labels
namespace: app-backend
annotations:
policies.kyverno.io/title: Add Labels
policies.kyverno.io/category: Sample
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Label
policies.kyverno.io/description: >-
This policy performs a simple mutation which adds labels
to Pods, Services, ConfigMaps, and Secrets.
spec:
rules:
- name: add-labels
match:
resources:
kinds:
- Pod
- Service
- ConfigMap
- Secret
mutate:
patchStrategicMerge:
metadata:
labels:
app.kubernetes.io/component: app-backend
app.kubernetes.io/part-of: my-big-app
app.kubernetes.io/managed-by: helm
EOF
On part du principe que notre namespace
contiendra uniquement des applications backend et que celles-ci appartiennent à une grosse application du nom de my-big-app, le tout déployé par Helm.
C’est parti pour tester cette mutation :
# Création du namespace
kubectl create ns app-backend
# Création du pod
kubectl -n app-backend run test-mutate --image=nginx:1.23.2
# Récupération des labels
kubectl -n app-backend get pod test-mutate -o yaml | grep -i "labels:" -A5
On retrouve bien nos trois labels définis :
labels:
app.kubernetes.io/component: app-backend
app.kubernetes.io/managed-by: helm
app.kubernetes.io/part-of: my-big-app
run: test-mutate
name: test-mutate
Stratégie de génération#
Comme dit plus haut, la partie generate
permet de créer des ressources supplémentaires à chaque création ou mise à jour de ressource. Je trouve cette partie extrêmement intéressante pour automatiser la création de ressources.
Par exemple, dès lors qu’un utilisateur, éventuellement un administrateur du cluster, souhaite créer un nouveau namespace
il peut être intéressant d’ajouter une NetworkPolicy
visant à restreindre les communications d’entrée et de sortie.
C’est ce que Kyverno met à disposition via cette stratégie avec la version brute ici.
...
rules:
- name: default-deny
match:
resources:
kinds:
- Namespace
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny
namespace: "{{request.object.metadata.name}}"
synchronize: true
data:
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
En prenant une partie de cette ClusterPolicy
, on s’aperçoit que celle-ci s’applique sur les namespace
en créant un objet de type NetworkPolicy
ayant pour nom default-deny
et qui s’applique au nom du namespace préalablement créé via cette instruction {{request.object.metadata.name}}
. La propriété synchronize
permet de garder la définition de l’objet même si une mise à jour intervient.
kubectl create -f https://raw.githubusercontent.com/kyverno/policies/main/best-practices/add_network_policy/add_network_policy.yaml
On va tester la création d’un namespace :
$ kubectl create ns test-ns
namespace/test-ns created
$ kubectl -n test-ns get netpol
NAME POD-SELECTOR AGE
default-deny <none> 25s
La NetworkPolicy
a bien été générée. Si on essaye de la supprimer, celle-ci sera recréée systématiquement grâce au paramètre synchronize: true
:
$ kubectl -n test-ns delete netpol default-deny
networkpolicy.networking.k8s.io "default-deny" deleted
$ kubectl -n test-ns get netpol
NAME POD-SELECTOR AGE
default-deny <none> 5s
Stratégie de vérification d’images#
Dernière étape, la vérification d’images avec cosign de Sigstore permettant entre autres de vérifier que les images sont signées par une clé publique.
Kyverno fournit un exemple disponible à cette adresse, il est également possible d’utiliser un Key Management Service (KMS) et intégrer la vérification de signature avec les services managés des fournisseurs de Cloud voire même celui de Vault.
Cette fonctionnalité est très intéressante pour valider la provenance des images et vérifier que celles-ci n’ont pas été altérées.
Conclusion#
Kyverno est un produit rapide et simple à mettre en place qui s’accompagne d’une multitude de stratégies sur étagère et prête à l’emploi.
Je suis convaincu qu’il doit être intégré dès l’installation d’un cluster Kubernetes pour suivre et renforcer l’utilisation des standards et bonnes pratiques en matière de sécurité.
Enfin, cet outil offre aussi plusieurs fonctionnalités et va plus loin que la simple validation de schéma pour les ressources à déployer. La génération, mutation de ressources et même la vérification d’images permettent d’ajouter une couche d’automatisation et de sécurité dans votre orchestrateur.
En conclusion, Kyverno est l’outil indispensable pour toute personne touchant de près ou de loin à l’univers Kubernetes.