Introduction
Les Custom Resource Definitions (CRD) de Kubernetes étendent l'API Kubernetes, mais le versioning introduit des risques de sécurité souvent négligés. Une CRD peut avoir plusieurs versions (par exemple, v1alpha1, v1beta1, v1), et chaque version peut avoir un schéma, des règles de validation et une logique de conversion différents. S'il n'est pas correctement sécurisé, le versioning peut entraîner une élévation de privilèges, une corruption des données ou des accès non intentionnels. Ce guide fournit une approche pratique pour durcir le versioning des CRD, avec des commandes concrètes, des exemples de configuration et des étapes de vérification.
Le public visé comprend les ingénieurs plateforme, les consultants DevOps et les équipes techniques de startups responsables de la maintenance des clusters Kubernetes. L'accent est mis sur la sécurité opérationnelle : observer avant de modifier, minimiser le rayon d'impact, utiliser des espaces réservés plutôt que des secrets, vérifier chaque étape et documenter les procédures de récupération.
Tout au long de ce guide, nous utilisons une CRD d'exemple appelée widgets.example.com pour illustrer les concepts. Nous supposons un cluster exécutant Kubernetes 1.25+ avec kubectl configuré et les autorisations appropriées pour créer des CRD et accéder au serveur d'API.
Inventaire des versions et de l'environnement
Avant d'effectuer toute modification, faites l'inventaire de l'état actuel de vos CRD et de leurs versions. Cela inclut l'identification des CRD installées, de leurs versions, du statut de stockage et des stratégies de conversion.
Commencez par lister toutes les CRD du cluster :
kubectl get crd
La sortie attendue comprend une liste des noms de CRD et leurs dates de création. Pour voir les détails d'une CRD spécifique, utilisez :
kubectl get crd widgets.example.com -o yaml
Cela affiche la spécification complète. Cherchez le champ spec.versions pour voir toutes les versions définies. Par exemple :
spec:
versions:
- name: v1beta1
served: true
storage: false
schema: ...
- name: v1
served: true
storage: true
schema: ...
Prêtez attention à :
- Quelle version est la version de stockage (une seule peut l'être).
- Quelles versions sont servies (les clients peuvent les utiliser).
- Si une stratégie de conversion est définie (par exemple,
NoneouWebhook).
Vérifiez si des webhooks de conversion sont configurés :
kubectl get crd widgets.example.com -o jsonpath='{.spec.conversion}'
Si la sortie inclut strategy: Webhook, alors la conversion est gérée par un webhook. Notez le service et le chemin du webhook, car c'est un risque de sécurité potentiel s'il n'est pas correctement authentifié.
Inspectez également les ressources personnalisées existantes de ce type pour voir quelles versions sont utilisées :
kubectl get widgets.example.com --all-namespaces -o wide
Le -o wide peut inclure la version dans la colonne VERSION. Sinon, utilisez -o custom-columns pour l'afficher :
kubectl get widgets.example.com --all-namespaces -o custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace,APIVERSION:.apiVersion
Enregistrez l'état actuel et les horodatages. Cet inventaire vous aide à comprendre l'impact de tout changement de versioning. Conservez une copie de la définition de la CRD et une liste des ressources avant de faire des modifications.
Liste de contrôle pratique pour l'inventaire de l'environnement :
kubectl versionpour confirmer les versions du client et du serveur.kubectl get crd widgets.example.com -o yaml > crd-backup.yamlpour sauvegarder la définition actuelle.kubectl get widgets.example.com -A -o json > widgets-backup.jsonpour sauvegarder toutes les instances.kubectl get apiservice | grep widgets.example.compour voir le statut du service d'API agrégé.kubectl describe crd widgets.example.compour voir les événements et les conditions de statut.
Chemin de configuration sécurisé
Lors de la configuration de la sécurité du versioning des CRD, suivez le principe du moindre privilège et validez tout. Le chemin sûr implique de définir des schémas appropriés, des RBAC et d'utiliser des webhooks de conversion de manière sécurisée.
1. Définir des schémas stricts
Chaque version de votre CRD doit avoir un schéma qui valide la structure et les contraintes. Utilisez la validation de schéma OpenAPI v3. Un schéma faible peut permettre des champs arbitraires, ce qui pourrait être exploité. Exemple pour v1 :
schema:
openAPIV3Schema:
type: object
required: ["spec"]
properties:
spec:
type: object
required: ["size"]
properties:
size:
type: integer
minimum: 1
maximum: 100
replicas:
type: integer
minimum: 0
maximum: 10
additionalProperties: false
additionalProperties: false
Définissez additionalProperties: false pour rejeter les champs inconnus. Cela empêche les attaquants d'injecter des données inattendues.
2. Implémenter des RBAC pour les versions de CRD
Les RBAC peuvent contrôler l'accès à des versions d'API spécifiques. Par défaut, les autorisations accordées sur une ressource s'appliquent à toutes les versions. Pour restreindre l'accès à certaines versions, vous pouvez utiliser resourceNames ou des rôles séparés. Par exemple, pour autoriser uniquement la lecture des ressources v1 mais pas v1beta1, créez un rôle :
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: widget-v1-reader
rules:
- apiGroups: ["example.com"]
resources: ["widgets"]
verbs: ["get", "list", "watch"]
# Pas de moyen de spécifier la version directement, mais vous pouvez utiliser resourceNames ou vous fier à l'agrégation.
En réalité, les RBAC de Kubernetes ne prennent pas en charge l'autorisation spécifique à une version directement. Le groupe d'API est spécifié, mais pas la version. Pour appliquer des restrictions de version, vous devez utiliser des webhooks d'admission ou des groupes d'API séparés par version. Alternativement, vous pouvez utiliser des politiques OPA/Gatekeeper. Nous couvrirons les webhooks d'admission plus tard.
3. Utiliser des webhooks de conversion de manière sécurisée
Si vous avez plusieurs versions, les webhooks de conversion permettent à Kubernetes de convertir les ressources entre les versions. Le webhook doit être servi via HTTPS avec un certificat valide. Configurez la conversion de la CRD avec caBundle pour faire confiance à l'autorité de certification du webhook. Exemple :
conversion:
strategy: Webhook
webhook:
conversionReviewVersions: ["v1", "v1beta1"]
clientConfig:
service:
namespace: conversion-webhook
name: conversion-service
path: /convert
caBundle: <certificat CA encodé en base64>
Définissez toujours conversionReviewVersions sur les versions que votre webhook prend en charge. Assurez-vous que le service du webhook n'est accessible qu'à l'intérieur du cluster et utilisez une authentification mutuelle TLS si possible.
4. Appliquer les modifications graduellement
Lors de la modification du versioning des CRD, évitez de supprimer ou de modifier brusquement les versions servies. Suivez un processus graduel :
- D'abord, ajoutez une nouvelle version avec
served: trueetstorage: false. - Permettez aux clients de migrer, puis définissez
storage: trueaprès les tests. - Dépréciez l'ancienne version en définissant
served: falseplus tard. - Enfin, supprimez l'ancienne version si aucune ressource ne l'utilise.
Pour vérifier quelles ressources utilisent encore une ancienne version :
kubectl get widgets.example.com --all-namespaces --field-selector apiVersion=example.com/v1beta1
Ou utilisez :
kubectl get widgets.example.com --all-namespaces -o json | jq '.items[] | select(.apiVersion=="example.com/v1beta1")'
Exemple : Mise à jour sécurisée d'une CRD
Supposons que vous souhaitiez ajouter v2 comme nouvelle version de stockage. Étapes :
- Sauvegardez la CRD actuelle :
kubectl get crd widgets.example.com -o yaml > crd-backup.yaml
- Modifiez la CRD pour ajouter v2 avec
served: trueetstorage: false:
kubectl edit crd widgets.example.com
Ajoutez sous spec.versions :
- name: v2
served: true
storage: false
schema: ...
Enregistrez et quittez.
- Vérifiez que v2 est servie mais pas en stockage :
kubectl get crd widgets.example.com -o jsonpath='{.spec.versions[*].name} {"\n"}'
kubectl get crd widgets.example.com -o jsonpath='{.spec.versions[?(@.storage==true)].name}'
La première sortie attendue liste toutes les versions incluant v2, la deuxième sortie montre encore v1.
- Testez la création d'une ressource v2 :
kubectl apply -f - <<EOF
apiVersion: example.com/v2
kind: Widget
metadata:
name: test-widget-v2
spec:
size: 10
EOF
- Après un test réussi, basculez le stockage vers v2 :
kubectl patch crd widgets.example.com --type='json' -p='[{"op": "replace", "path": "/spec/versions/1/storage", "value": true}, {"op": "replace", "path": "/spec/versions/0/storage", "value": false}]'
(En supposant que l'ordre du tableau des versions : index 0 = v1, index 1 = v2). Ajustez les chemins en conséquence.
- Vérifiez que la version de stockage a changé :
kubectl get crd widgets.example.com -o jsonpath='{.spec.versions[?(@.storage==true)].name}'
Sortie : v2.
Vérification et diagnostic
Après avoir effectué des modifications, vérifiez que tout fonctionne comme prévu et diagnostiquez tout problème.
Vérifier le statut de la CRD
Utilisez kubectl describe crd pour voir les conditions de statut :
kubectl describe crd widgets.example.com
Cherchez la condition Established à true et aucun avertissement NonStructuralSchema. Si le schéma n'est pas structurel, Kubernetes peut rejeter la CRD ou ne pas la servir.
Tester les opérations sur les ressources
Essayez de créer, obtenir et lister des ressources dans chaque version :
# Créer une ressource v1
kubectl apply -f - <<EOF
apiVersion: example.com/v1
kind: Widget
metadata:
name: test-v1
spec:
size: 5
EOF
# Obtenir via v1
kubectl get widgets.v1.example.com test-v1 -o yaml
# Obtenir via v2 (la conversion devrait avoir lieu si le webhook est configuré)
kubectl get widgets.v2.example.com test-v1 -o yaml
Si la conversion n'est pas définie, la récupération via v2 échouera car la version de stockage est v2 mais l'objet a été créé avec v1 ? En réalité, si le stockage est v2 et que vous créez une ressource v1, Kubernetes la convertira en v2 pour le stockage si un webhook de conversion existe, ou si aucune conversion, il la rejettera. Vérifiez le comportement attendu.
Vérifier les journaux d'audit
Activez la journalisation d'audit de Kubernetes pour surveiller l'accès aux versions de CRD. Cherchez les requêtes vers /apis/example.com/v1beta1/widgets si vous essayez de détecter l'utilisation de versions dépréciées. Exemple de fragment de politique d'audit :
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: "example.com"
resources: ["widgets"]
Ensuite, consultez les journaux d'audit avec des outils comme kubectl logs -n kube-system kube-apiserver-<node> ou si vous utilisez un Kubernetes géré, utilisez la journalisation du fournisseur cloud.
Diagnostiquer les échecs des webhooks de conversion
Si le webhook de conversion est mal configuré, vous pouvez voir des erreurs comme :
Vérifiez les journaux du pod du webhook :
conversion webhook not reachablex509: certificate signed by unknown authority
kubectl logs -n conversion-webhook deploy/conversion-service
Testez l'endpoint du webhook directement depuis l'intérieur du cluster :
kubectl run -it --rm debug --image=curlimages/curl -- sh
# dans le pod :
curl -k https://conversion-service.conversion-webhook.svc/convert -d '{"apiVersion":"apiextensions.k8s.io/v1","kind":"ConversionReview",...}'
Assurez-vous que caBundle est correctement encodé en base64. Utilisez base64 -w0 sur Linux.
Modes de défaillance et récupération
Comprenez les modes de défaillance courants et comment récupérer.
Défaillance : CRD avec schéma invalide provoque des erreurs du serveur d'API
Si vous appliquez une CRD avec un schéma non structurel ou un OpenAPI invalide, le serveur d'API peut la rejeter. Exemple d'erreur :
The CustomResourceDefinition "widgets.example.com" is invalid: spec.validation.openAPIV3Schema.properties[spec].type: Required value: must not be empty at root
Récupération : corrigez le schéma et réappliquez. Si la CRD fonctionnait auparavant et que vous avez commis une erreur, restaurez à partir de la sauvegarde :
kubectl apply -f crd-backup.yaml
Défaillance : Suppression d'une version encore utilisée
Si vous définissez served: false pour une version qui a des ressources, ces ressources deviennent inaccessibles via l'API, bien qu'elles restent dans etcd. Vous devez les migrer avant de supprimer le service. Pour récupérer, définissez à nouveau served: true et effectuez la migration.
Défaillance : Webhook de conversion hors service
Si le webhook de conversion est hors service, les requêtes nécessitant une conversion échoueront. Exemple d'erreur : conversion webhook not reachable. Récupération : corrigez le déploiement du webhook. Si le webhook ne peut pas être restauré rapidement, vous pouvez changer la stratégie conversion en None temporairement, mais cela peut entraîner une incohérence des données si plusieurs versions existent. Il est préférable de restaurer le webhook.
Défaillance : Changement de version de stockage entraîne une perte de données
Changer la version de stockage sans conversion appropriée peut entraîner une perte de données si des champs sont supprimés. Assurez-vous toujours que le webhook de conversion est en place et testé avant de basculer le stockage. Si une perte de données se produit, restaurez à partir d'une sauvegarde etcd si disponible.
Étapes de récupération
- Identifiez l'opération en échec et les messages d'erreur.
- Vérifiez les événements du cluster :
kubectl get events --sort-by='.lastTimestamp'. - Vérifiez les journaux du serveur d'API pour des erreurs détaillées (nécessite un accès au plan de contrôle).
- Sauvegardez l'état actuel :
kubectl get crd widgets.example.com -o yaml > crd-current.yaml. - Tentez de revenir à la CRD précédemment bonne :
kubectl apply -f crd-backup.yaml. - Si des ressources sont manquantes ou corrompues, restaurez à partir d'un instantané etcd (voir la récupération après sinistre etcd).
- Après la récupération, vérifiez avec des requêtes en lecture seule avant d'activer les écritures.
Liste de contrôle opérationnelle
Utilisez cette liste de contrôle avant et après avoir apporté des modifications de versioning.
| # | Contrôle | Commande / Action | Résultat attendu |
|---|---|---|---|
| 1 | Sauvegarder la CRD | kubectl get crd widgets.example.com -o yaml > crd-backup-$(date +%Y%m%d).yaml | Fichier créé avec la définition actuelle de la CRD |
| 2 | Lister les ressources existantes par version | kubectl get widgets.example.com -A -o json | jq -r '.items[] | .apiVersion' | sort | uniq -c | Nombre de ressources par version |
| 3 | Vérifier la stratégie de conversion | kubectl get crd widgets.example.com -o jsonpath='{.spec.conversion.strategy}' | None ou Webhook |
| 4 | Valider le schéma hors ligne | Utilisez kubectl apply --dry-run=client -f crd-new.yaml | Aucune erreur de schéma |
| 5 | Tester d'abord sur un cluster de staging | Appliquez les modifications à un cluster non-production | Aucun comportement inattendu |
| 6 | Surveiller les journaux d'audit | Suivez les journaux d'audit pour le groupe example.com | Seulement les requêtes attendues |
| 7 | Vérifier le service de chaque version | kubectl get crd widgets.example.com -o jsonpath='{range .spec.versions[*]}{.name}{" served="}{.served}{" storage="}{.storage}{"\n"}{end}' | Indicateurs served/storage corrects |
| 8 | Tester le CRUD des ressources dans toutes les versions servies | Script qui crée, obtient, liste, supprime une ressource de test dans chaque version | Succès sans erreurs |
| 9 | Vérifier l'utilisation de versions dépréciées | kubectl get widgets.example.com -A -o json | jq '.items[] | select(.apiVersion | contains("v1beta1"))' | Liste vide (aucune ressource utilisant une version dépréciée) |
| 10 | Documenter la procédure de rollback | Rédiger un runbook pour annuler les modifications de CRD | Étapes claires pour la récupération |
Conclusion
Sécuriser le versioning des CRD Kubernetes est essentiel pour maintenir un système d'extension d'API robuste et sûr. En suivant les pratiques de ce guide—inventorier les versions, définir des schémas stricts, contrôler l'accès, utiliser des webhooks de conversion de manière sécurisée et vérifier les modifications—vous pouvez prévenir de nombreux problèmes de sécurité et opérationnels courants.
Commencez par une vérification à faible risque : inventoriez vos CRD actuelles, identifiez leurs versions et paramètres de conversion, et exécutez les commandes de la liste de contrôle. Ensuite, implémentez progressivement des améliorations, en testant chaque changement dans un environnement de staging. N'oubliez jamais de sauvegarder avant de faire des modifications et d'avoir un plan de retour en arrière.
Un flux de travail fiable rend les échecs visibles, protège les données sensibles, limite les changements aux ressources prévues et définit la récupération avant qu'un incident ne force la décision. Appliquez ces principes pour durcir dès aujourd'hui le versioning de vos CRD Kubernetes.