[Sécurité] L'agent s'exécute en cluster-admin et exécute du code arbitraire issu des packages OCI #13
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Constat
Le ServiceAccount
vynil-agentest lié àcluster-adminet à un ClusterRole*/*/*maison.Dans
box/vynil/systems/rbac.yaml.hbs:Le bootstrap fait de même (
deploy/bootstrap/bootstrap.yaml) :vynil-bootstrap→ ClusterRole*/*/*.Pourquoi c'est un risque
L'agent exécute du code arbitraire fourni par les packages : scripts Rhai unpackés depuis l'image OCI, plus, dans le moteur Rhai core, des primitives shell et système exposées à tous les packages (
common/src/shellhandler.rs,common/src/rhaihandler.rs) :Conséquence : installer un package = exécution de code arbitraire avec les droits cluster-admin. Un package malveillant ou compromis (ou une jukebox/registre compromis) obtient le contrôle total du cluster : lecture de tous les secrets, création de pods privilégiés, exfiltration, persistance. Il n'y a aucune isolation entre un package « tenant » (censé être cantonné à son namespace) et le reste du cluster au niveau RBAC : c'est le code Rhai qui est censé se restreindre, mais rien ne l'y contraint techniquement.
C'est cohérent avec le modèle « distribution intégrée » de Vynil, mais le niveau de privilège mérite d'être explicité et, si possible, réduit.
Pistes
vynil-agentcluster-admin partagé.shell_run,shell_output,get_env, accès fichiers) aux seuls packagessystem/corede confiance ; les retirer du moteur exposé aux packagestenant. Le linter mentionne déjà des notions de « api mode » / « no tenant access in system packages » — étendre cette logique au runtime, pas seulement au lint.Note liée
L'efficacité de la signature Cosign (#issue dédiée) dépend directement de ce point : tant que n'importe quel package tourne en cluster-admin, la vérification de signature à l'installation est la seule barrière de défense en profondeur.
Techniquement, c'est une question de trust. Mais oui ce problème a été vu day 1 dans le design.
Le niveau de trust doit être porté par le rbac k8s :
Au fond, vynil reproduit la structure debian : seul root peut déterminer quels sont les sources d'apt. Seul root peut installer des packages. Ok avec vynil, on permets aux utilisateurs d'installer leur propre applications, mais ces applications sont limité dans leur scope : seulement les packages de type tenant.
Pour le code, en rhai, il existe 2 modes d'installation, un mode spécifique pour le mode tenant et un mode pour les packages system/service qui permet tout (il faut pouvoir installer capsule, fluxcd ou argocd (et bien d'autres) qui eux aussi ont un clusterrolebinding cluster-admin, pour pouvoir créer ce clusterrolebinding il faut l'être aussi). Mais déjà, nous avons plusieurs package de type tenant, qui, pour de bonnes raisons, contournent le système automatique et utilisent des hooks pour installer 1-2 objets qui ne devrait pas être dans leur périmètre de droit. Ces packages par eux-même démontrent que ce n'est pas si simple.
Autre information, pour vynil, la seul définition d'un tenant est un ensemble de namespace qui partage un couple clé/valeur dans leur labels. Rien de plus rien de moins. (Ce comportement est customisable et est customisé dans les 2 distribution vynil existantes. les 2 customisations partent dans 2 directions différentes. On ne peux pas réduire le champs de la définition actuelle d'un tenant.
Enfin, mon idée initiale était de permettre la configuration d'un template pour définir quel est le service-account a utiliser. Et laisser à la distribution le soin de gérer les droits de ce ServiceAccount (via un package système qui matérialise la création du tenant, et donc gère ce service account). C'est encore le plan. Reste a le mettre en oeuvre, mais toute les briques sont là pour ca.
Pour autant, un quick fix intéressant serait d'enlever les méthodes suivante pour une installation/désinstallation :
Ces méthodes ne font du sens que pour aider le packaging ou pour les backup/restauration
Alignement après ton retour et l'analyse de packages en production :
La piste 1 de l'issue (RBAC calculé depuis le contenu du paquet) est retirée. L'observation de paquets
tenantmatures le confirme : plusieurs gèrent légitimement, via hooks, des objets hors de leur périmètre théorique (patch de CR d'opérateurs tiers, suppression de PVC créés indirectement). Un RBAC dérivé du contenu déclaré serait soit trop strict (casse ces paquets), soit troué au point d'être inutile. La direction « template de ServiceAccount par tenant, droits gérés par le paquet système qui matérialise le tenant » est la bonne — elle laisse chaque distribution définir sa propre frontière, ce qui colle au fait que la définition d'un tenant est volontairement minimale (un couple clé/valeur de labels) et déjà customisée différemment selon les distributions.Quick fix retenu : retirer
shell_run/shell_output/get_envdu moteur Rhai pour les opérations install / delete / reconfigure, en les conservant pour le packaging (package build/update) et pour backup / restore (restic est piloté par shell). Concrètement : un mode d'enregistrement des primitives par opération dans le handler Rhai, plutôt qu'un moteur unique. Le lint connaît déjà la notion d'« api mode » — le runtime devient cohérent avec ce que le lint promet.Documentation : le modèle de menace (« installer un paquet = confiance équivalente cluster-admin », jukebox réservées aux cluster-admins, types system/service réservés aux admins) est désormais explicité dans la doc (
docs/operations/security.md), comme suggéré.Analyse et rédaction : Claude (assistant IA), publié via le compte de Xavier.
Le mode d'installation pour les packages de type tenant qui utilise un SA dédié n'est en place que de manière théorique et que dans ma tête : ca n'a jamais été testé ni validé.
A mon sens, c'est la stratégie a suivre ici.
Il faut valider que le cablage est bien en place puis vérifier que ca marche.
Quand au Quick Fix, je suis pas sûr qu'il soit retenu, j'indiquais juste que c'est une piste. Si on regarde vraiment cette piste, c'est as un quick fix, c'est une solution pour rendre ca modulaire. Aujoud'hui au final on a 2 monolithes le monolithe de prod et le monolithe de test. Pour avancer dans cette piste ils faut 2 structure modulaire qui serait synchrones en fonction des états attendu en réél et en test. Non l'implémentation de cette piste n'est pas triviale