Set up Kyverno
Estimated time to read: 8 minutes
Pour contrôler ce qu'il se passe dans notre cluster, nous allons utiliser kyverno.
Installation
Kyverno fournit un Helm chart pour simplifer l'installation.
Une fois configuré, on peut installer le Helm chart:
Success
On vérifie que tout est ok coté namespace kyverno
Les composants installés par kyverno
NAME READY STATUS RESTARTS AGE
pod/kyverno-admission-controller-547894dbc9-srg54 1/1 Running 0 2m43s
pod/kyverno-background-controller-696f7765d-d8knf 1/1 Running 0 2m43s
pod/kyverno-cleanup-controller-567bb6695c-mxf7s 1/1 Running 0 2m43s
pod/kyverno-reports-controller-5799587486-x2gjp 1/1 Running 0 2m43s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kyverno-background-controller-metrics ClusterIP 10.3.236.41 <none> 8000/TCP 2m45s
service/kyverno-cleanup-controller ClusterIP 10.3.230.129 <none> 443/TCP 2m45s
service/kyverno-cleanup-controller-metrics ClusterIP 10.3.189.211 <none> 8000/TCP 2m45s
service/kyverno-reports-controller-metrics ClusterIP 10.3.153.42 <none> 8000/TCP 2m45s
service/kyverno-svc ClusterIP 10.3.165.95 <none> 443/TCP 2m45s
service/kyverno-svc-metrics ClusterIP 10.3.106.166 <none> 8000/TCP 2m45s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kyverno-admission-controller 1/1 1 1 2m44s
deployment.apps/kyverno-background-controller 1/1 1 1 2m44s
deployment.apps/kyverno-cleanup-controller 1/1 1 1 2m44s
deployment.apps/kyverno-reports-controller 1/1 1 1 2m44s
NAME DESIRED CURRENT READY AGE
replicaset.apps/kyverno-admission-controller-547894dbc9 1 1 1 2m44s
replicaset.apps/kyverno-background-controller-696f7765d 1 1 1 2m44s
replicaset.apps/kyverno-cleanup-controller-567bb6695c 1 1 1 2m44s
replicaset.apps/kyverno-reports-controller-5799587486 1 1 1 2m44s
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
cronjob.batch/kyverno-cleanup-admission-reports */10 * * * * False 0 <none> 2m44s
cronjob.batch/kyverno-cleanup-cluster-admission-reports */10 * * * * False 0 <none> 2m44s
Ma première policy
Comme nous l'a demandé le chef de brigade, on veut contrôler nos ingrédients : mettons en place une policy pour limiter l'usage d'images Docker provenant uniquement d'une registry privée en laquelle on a confiance, dans notre demo, le registry de gitlab.com de notre projet.
Définissons notre policy ClusterPolicy
qui sera à l'échelle du cluster complet (les ➕ vous donnent des indications pour mieux comprendre)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
annotations:
policies.kyverno.io/title: Restrict Image Registries
policies.kyverno.io/description: >-
Only images from specific gitlab.com registry are allowed.
spec:
validationFailureAction: Enforce # (1)
background: true
rules:
- name: validate-registries
match:
any: # (2)
- resources:
kinds:
- Pod
exclude: # (3)
any:
- resources:
namespaces:
- kube-system
- external-dns
- external-secrets
- nginx-ingress-controller
- kyverno
- cert-manager
validate: # (4)
message: "Unauthorized registry."
pattern:
spec: # (5)
=(ephemeralContainers):
- image: "registry.gitlab.com/*"
=(initContainers):
- image: "registry.gitlab.com/*"
containers:
- image: "registry.gitlab.com/*"
- 💪 On utilise une action de type
Enforce
qui est bloquante. Il existe aussi le typeAudit
qui est un warning mais non bloquant - 🔍 On applique à tous les Pod
- 🚸 On filtre les namespaces que l'on a déjà instancié et qui sont des namespaces d'administration
- 👽 Le type de règle, ici
validate
- 📝 On spécifie pour chaque type, les origines autorisées
On peut déployer notre policy:
et on vérifie qu'elle est bien opérationnelle
Policy au statut Ready
Vérification
Essayons de démarrer un pod avec une image issue de Docker hub:
Impossible de démarrer le pod
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/demos/demo-nginx was blocked due to the following policies
restrict-image-registry:
validate-registries: 'validation error: Unauthorized registry. rule validate-registries
failed at path /spec/containers/0/image/'
On met à jour notre Deployment
pour utiliser une image issue de la registry GitLab:
apiVersion: apps/v1
kind: Deployment
# ...
spec:
selector:
matchLabels:
app: nginx-hardened
template:
metadata:
labels:
app: nginx-hardened
spec:
containers:
- image: registry.gitlab.com/yodamad-workshops/kub-workshop/asciinematic:latest # (1)
name: asciinematic-hardened
ports:
- containerPort: 80
imagePullSecrets:
- name: gitlabcred # (2)
- 🐳 On utilise une image de notre registry privée
- 🔐 C'est une registry privée, il faut s'authentifier...
On voit qu'il faut ajout un secret pour être capable de pull
une image depuis une registry privée, normal...
Mais du coup, il faut créer un secret pour cela. Alors on va en créer un
kubectl create secret gitlabcred regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>
Le chef de la brigade de la sécurité
Pas de secret en dur dans mon cluster !! 🤬
Oups, il a pas tort 😅 On devrait plutôt utiliser external-secrets
On crée un ExternalSecret
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: external-secret-gitlabcred
namespace: kyverno
spec:
refreshInterval: 1m
secretStoreRef:
name: gitlab-cluster-secret-store
kind: ClusterSecretStore
target:
name: gitlabcred
creationPolicy: Owner
template:
engineVersion: v2
type: kubernetes.io/dockerconfigjson # (1)
data: # (2)
.dockerconfigjson: "{\"auths\":{\"registry.gitlab.com\":{\"username\":\"{{ .username }}\",\"password\":\"{{ .password }}\",\"auth\":\"{{(printf \"%s:%s\" .username .password) | b64enc }}\"}}}"
data: # (3)
- secretKey: username
remoteRef:
key: gl_cr_username
- secretKey: password
remoteRef:
key: gl_cr_password
- 🐳 On définit le type de template que l'on utilise pour créer le secret
- 💽 On crée le dockerconfigjson à partir de 2 variables
- 🦊 On récupère le username & le mot de passe depuis GitLab
que l'on déploie
Secret correctement créé
L'ExternalSecret
est créé et synchronisé
NAME STORE REFRESH INTERVAL STATUS READY
external-secret-gitlabcred gitlab-cluster-secret-store 1m SecretSynced True
et le secret aussi
On doit pouvoir déployer notre nouvelle image
Pour les curieux, le fichier kyverno-asciinematic-hardened.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: asciinematic-hardened
namespace: demos
spec:
selector:
matchLabels:
app: asciinematic-hardened
template:
metadata:
labels:
app: asciinematic-hardened
spec:
containers:
- image: registry.gitlab.com/yodamad-workshops/kub-workshop/asciinematic:latest
name: asciinematic-hardened
ports:
- containerPort: 80
imagePullSecrets:
- name: gitlabcred
Image déployée
Une 2ème policy
Du fait que l'on a forcé sur l'ensemble du cluster que les images proviennent de GitLab, il serait pratique qu'automatiquement lorsque l'on crée un namespace, automatiquement le secret pour se connecter à GitLab via external-secrets
se crée.
On a utilisé un type validate
lors de notre première policy, dans ce second cas, on va utiliser le type generate
qui va automatiquement générer des éléments.
# ...
policies.kyverno.io/description: >-
Secrets like registry credentials often need to exist in multiple
Namespaces so Pods there have access. Manually duplicating those Secrets
is time consuming and error prone. This policy will copy a
Secret called `gitlabcred` which exists in the `kyverno` Namespace to
new Namespaces when they are created. It will also push updates to
the copied Secrets should the source Secret be changed.
spec:
rules:
- name: sync-image-pull-secret
# ...
generate: # (1)
apiVersion: v1
kind: Secret
name: regcred
namespace: "{{request.object.metadata.name}}"
synchronize: true
clone: # (2)
namespace: kyverno
name: gitlabcred
- 👽 Type
generate
- 🧬 Action de clone d'un object existant
Déployons cette nouvelle policy
Pour les curieux, le fichier kyverno-sync-regcred.yml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: sync-secrets
annotations:
policies.kyverno.io/title: Sync Secrets
policies.kyverno.io/category: Sample
policies.kyverno.io/subject: Secret
policies.kyverno.io/minversion: 1.6.0
policies.kyverno.io/description: >-
Secrets like registry credentials often need to exist in multiple
Namespaces so Pods there have access. Manually duplicating those Secrets
is time consuming and error prone. This policy will copy a
Secret called `gitlabcred` which exists in the `kyverno` Namespace to
new Namespaces when they are created. It will also push updates to
the copied Secrets should the source Secret be changed.
spec:
rules:
- name: sync-image-pull-secret
match:
any:
- resources:
kinds:
- Namespace
generate:
apiVersion: v1
kind: Secret
name: regcred
namespace: "{{request.object.metadata.name}}"
synchronize: true
clone:
namespace: kyverno
name: gitlabcred
On vérifie que la policy est créée et opérationnelle
Policy créée et opérationnelle
- 🚔 On retrouve bien notre première policy
Vérification
Vérifions que notre nouvelle policy fonctionne bien en créant un nouveau namespace
Normalement, on devrait avoir un secret dans notre nouveau namespace
Voyons si l'on essaie de déployer une image provenant de notre registry privée dans notre nouveau namespace
kubectl run demo-nginx --image=registry.gitlab.com/yodamad-workshops/kub-workshop/nginx:hardened -n demo-kyverno
Image déployée
Et vérifions que notre première policy est bien valable dans notre nouveau namespace
Impossible de déployer
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/demo-kyverno/demo-nginx was blocked due to the following policies
restrict-image-registry:
validate-registries: 'validation error: Unauthorized registry. rule validate-registries
failed at path /spec/containers/0/image/'
Suivi de nos policies
Kyverno permet de facilement voir les policies qui ont été exécutées
La policy a été exécutée avec succès
On peut aussi vérifier à l'échelle du cluster
L'ensemble des reports du cluster
NAMESPACE NAME PASS FAIL WARN ERROR SKIP AGE
demo-kyverno 3e3ad989-01be-46fc-afc9-7eea0f78a74c 1 0 0 0 0 14m
demos 4649657f-4994-47eb-a15b-a3e2b14e82db 0 1 0 0 0 88m
demos 718a97de-06af-4df8-b7d0-17f7ca9b5d02 1 0 0 0 0 34m
demos 81d369ad-0b5a-46ba-85b6-0e73e7572afe 0 1 0 0 0 88m
On voit que les policies se sont exécutées sur les composants installés avant la policy et que certains sont aussi status FAIL
Pour aller plus loin
Kyverno propose d'autres types de policies.
Article sur le sujet #autopromo
Un article décrivant plus en détails cela est dispo sur le blog
Notre cocotte est sécurisée et avec des bons produits issus de la filière locale 😉
Il est temps de se reposer un peu ➡️