Dette technique : ma bibliothèque Rust est désormais passée en CDO
(lucumr.pocoo.org)- Dans l’écosystème Rust, dès qu’une dépendance abandonnée apparaît dans RUSTSEC, même une bibliothèque qui ne posait pas directement problème devient une dette technique via ses utilisateurs et la CI
instadépendait deyaml-rust, où les demandes de fonctionnalités et les bugs s’étaient accumulés après le désintérêt progressif de son auteur initial- Après l’inscription dans RUSTSEC, les CI des utilisateurs directs et indirects ont échoué ; en termes financiers, cela revient à une dégradation de note et à un appel de marge
- Les bibliothèques de remplacement ou les forks n’étaient pas non plus des solutions évidentes : même en changeant de dépendance, la charge de maintenance et le risque lié aux nouvelles dépendances subsistent
- La réponse finale a consisté à vendoriser le code de
yaml-rustdansinsta, ce qui a conduit à la critique selon laquelle cela ressemblait à un CDO empaquetant une mauvaise dette technique enAAA
Comment la dépendance yaml-rust s’est révélée être une dette technique
instadépendait deyaml-rust, et les issues s’y accumulaient depuis que son auteur initial s’en était désintéressé- Certaines étaient des demandes de fonctionnalités, d’autres de vrais bugs
- Le mainteneur de
instane rencontrait pas directement ces problèmes, mais le fait qu’il s’agisse d’une dépendance abandonnée en faisait une dette technique
- La situation a changé lorsque
yaml-rusta fait l’objet d’une discussion pour son ajout à la base de données RUSTSEC- Dans l’analogie financière, RUSTSEC joue le rôle d’agence de notation
- Après l’inscription, la CI de nombreux projets utilisant
yaml-rustdirectement ou indirectement a commencé à échouer en quelques minutes - Des utilisateurs ont signalé au mainteneur de
instale problème lié à l’utilisation deyaml-rust, ce qui correspond, dans l’analogie financière, à un appel de marge
Les options et la réponse réelle
- Passer à une alternative n’était pas très séduisant
- L’une des alternatives est un fork de
yaml-rust, avec un seul mainteneur, et ajoute trois dépendances - L’une d’elles avait déjà reçu une note « B- »
- Une autre option de l’écosystème avait pris la décision de changer les valeurs par défaut avant d’être mise en cause
- L’une des alternatives est un fork de
- Faire son propre fork n’était pas non plus une solution de fond
- Une bibliothèque forkée entraîne les mêmes exigences de maintenance
- Si les rapports de bugs ne sont pas traités, elle finira par être pointée du doigt comme l’ancien
yaml-rust - Un fork peut donc faire gagner du temps, mais ne fait pas disparaître le problème
- La réponse réelle a été la vendorisation, en intégrant le code de
yaml-rustdansinstainstaest désormais une combinaison du code deinstaet deyaml-rust- C’est une structure qui revient à rehausser une mauvaise dette technique en
AAA - Le CDO du titre désigne les obligations adossées à des actifs, rendues tristement célèbres lors de la crise financière de 2007
- Le bilan final est proche de « personne n’a gagné »
- Le code problématique n’a pas disparu ; il a simplement été déplacé à l’intérieur de
insta - La pression liée à son apparence de dépendance externe a diminué, mais la charge de maintenance elle-même demeure
- Le code problématique n’a pas disparu ; il a simplement été déplacé à l’intérieur de
1 commentaires
Avis Hacker News
Pour parler franchement, l’auteur du parseur YAML le plus populaire, serde_yaml(https://lib.rs/crates/serde_yaml), a soudainement lâché l’affaire sans préavis ni désignation d’un mainteneur successeur, et l’a marqué comme deprecated et unmaintained.
Ce n’est pas exactement comme left-pad. Le package fonctionne toujours et crates.io n’autorise pas non plus sa suppression, mais ce package est utilisé par 4 000 autres crates.
Les outils d’audit et de mise à jour automatique vont commencer à signaler comme problématique l’utilisation d’une crate non maintenue.
En même temps, serde_yaml a aussi été marqué comme unmaintained afin d’empêcher que les gens migrent massivement vers lui.
J’aimerais que les outils d’audit et les politiques d’entreprise soient assez intelligents pour distinguer ces cas au lieu de continuer à alerter sur « non maintenu » au seul motif qu’il n’y a pas eu de commit récent.
Je me demande s’il y a plus d’informations.
L’acronyme CDO, sorti sans explication, ne m’a pas été immédiatement clair, mais vu que le texte emploie plusieurs fois le terme collateralized, il s’agit probablement d’une obligation adossée à des actifs (collateralized debt obligation).
https://en.wikipedia.org/wiki/Collateralized_debt_obligation
Au début, j’ai pensé à chief data officer.
Plus précisément, en 2008, il s’agissait de quelque chose comme une propriété partielle sur plusieurs prêts hypothécaires, et lorsque les prêts hypothécaires pourris ont fait défaut, les CDO se sont effondrés avec eux [1].
Quoi qu’il en soit, ce que l’auteur a en tête ressemble moins à une métaphore fondée sur la dette qu’à un risque systémique, comme leftpad ou une panne DNS en cascade. L’une des grandes raisons pour lesquelles 2008 a été un tel chaos tenait davantage à des problèmes systémiques qu’au simple fait d’avoir créé des produits à partir de dettes [2].
Cela signifie aussi que les agences de notation censées jouer un rôle de régulateur ont déjà été capturées.
Que ce soit une « victoire » dépend tellement de la définition de « victoire » que je n’ai pas envie d’en débattre, mais il y a clairement des avantages. Un chemin de code vulnérable qui ne s’exécute pas et qui n’est pas atteignable depuis une bibliothèque externe devient maintenant un chemin de code sûr.
Bien sûr, la situation reste préoccupante, mais elle est sûre.
Ce genre de vendoring a aussi un autre avantage. Si votre bibliothèque dispose d’une couverture de tests solide, vous pouvez lancer des outils de couverture de code sur la bibliothèque nouvellement importée.
Modifier la bibliothèque peut être difficile, mais il peut être relativement facile de supprimer progressivement les parties que votre propre code ne touche pas. Cela dépend de la structure, mais si tout le code vulnérable est supprimé, le gain est évident ; et si vous découvrez que vous en utilisiez en fait une partie, le gain peut être encore plus clair.
Autrement dit, forker une bibliothèque pour en assurer la maintenance publique est une lourde responsabilité, mais vendorer uniquement les parties dont on a besoin et les retoucher représente une charge bien moindre. Même sans élagage effectif, le simple fait de rendre cela facile est déjà un progrès.
Il y a évidemment aussi des inconvénients, mais tout n’est pas mauvais.
Mais si plus personne ne surveille une dépendance externe, cet argument disparaît, et cette dépendance devient un fardeau.
Le fait que la dépendance soit marquée comme abandoned et commence à apparaître dans les rapports de sécurité est le comportement souhaité. Cela permet de décider en connaissance de cause s’il faut la vendorer, c’est-à-dire supprimer le risque qu’un « mauvais acteur y glisse quelque chose en douce », ou choisir une autre option.
C’est aussi bien que le système de build de Rust rende cela facile.
En général, une seule version de cette dépendance est aussi autorisée dans l’ensemble de l’immense monorepo. Cela a peut-être changé depuis mon passage.
Et chaque dépendance third_party a un responsable désigné ou des OWNERS.
Cela permet de maintenir un ordre relatif.
Cela dit, ce type de discipline imposée peut bien convenir à une organisation comme Google, qui dispose de suffisamment de temps et d’argent et n’est pas prisonnière de la philosophie « move fast and break things ». Je ne sais pas dans quelle mesure cela fonctionnerait bien dans une startup.
Dans un certain sens, comme les autres bibliothèques fournissent des appels presque aléatoires, cela pourrait constituer une stratégie de test intéressante, proche d’un fuzz test orienté.
J’ai observé le même schéma dans l’écosystème JS npm
npm audit se comporte le plus souvent comme le garçon qui criait au loup en matière de sécurité, et, si la licence le permet, rapatrier le code en interne est l’un des moyens les plus stables de ne pas être submergé par de faux problèmes signalés par les utilisateurs
Souvent, les utilisateurs ne comprennent pas le contexte, ou s’en moquent parce que les politiques de leur employeur ont été élaborées dans un endroit déconnecté de la réalité
Une expression régulière utilisée dans une partie du code d’une dépendance transitive de la pipeline de build ne peut pas forcément être exploitée en pratique pour une attaque par déni de service
Les « problèmes » dans des dépendances transitives profondes peuvent être particulièrement pénibles à contourner. Structurellement, il est souvent difficile de prouver techniquement des faits comme « nous n’empruntons jamais ce chemin de code, donc nous ne sommes pas affectés par la faille » ou « le seul cas où ce chemin est emprunté implique une entrée de confiance dans un environnement hors ligne »
Il existe bien des scénarios où ce genre de problème compte. Par exemple, un outil de build compromis peut injecter du code malveillant dans la bibliothèque en cours de compilation
Mais ces cas sont extrêmement rares, et ils se retrouvent noyés dans une vague d’expressions régulières pouvant potentiellement provoquer un déni de service, qui en pratique n’ont pas d’importance parce qu’elles ne sont invoquées que pendant le build
Ajoutez à cela le fait qu’un outil de build courant traîne un arbre d’environ cinquante milliards de dépendances transitives, et cela devient vraiment pénible
À mon avis, les outils qui signalent ce type de problèmes devraient distinguer « exploitable en cas de redistribution » et « exploitable en cas d’utilisation dans la pipeline de build »
Dans « une dette technique douteuse vient soudain de recevoir une note AAA », le mot « soudain » semble vouloir dire qu’il n’a pas de sens que le même code reçoive une meilleure notation de dette simplement parce qu’il a été vendorizé
Mais cela ne considère que la valeur du code lui-même, et passe à côté de la partie la plus importante de la proposition de valeur globale
Quand un mainteneur rapatrie le code en interne, ce code devient désormais sa propriété. Si un mainteneur actif vendorise le code d’un projet mort, il existe alors une personne active capable de répondre aux issues, de relire les pull requests et de corriger les bugs, ce qui augmente la valeur de ce code
Pour prendre une autre analogie, c’est comme confier un animal de compagnie négligé à un nouveau propriétaire : il sera mieux soigné, en meilleure santé et vivra plus longtemps, donc sa valeur augmente
Le mainteneur devra aussi s’adapter à une grosse base de code inconnue, ce qui créera une barrière à l’entrée pour implémenter des correctifs ou les relire
C’est un peu hors sujet et controversé, mais je pense qu’un gestionnaire de paquets fondé sur les sources aura du mal à éviter de terribles problèmes s’il ne garantit pas le droit légal pour le registre de reprendre de force la maintenance des paquets publiés
Je parle de problèmes comme l’abandon, les modifications malveillantes, la suppression malveillante ou l’usurpation d’identité
Si l’on juge qu’un paquet est suffisamment important pour une communauté plus large, il faut un moyen de retirer l’entrée du registre des mains de son propriétaire initial et de la faire pointer vers un fork
Évidemment, ce genre de mesure entraînera beaucoup de drames, mais elle peut protéger activement les utilisateurs en aval
Le vrai problème, c’est que maintenir un projet open source demande du temps et des efforts, et qu’il n’est pas facile de trouver quelqu’un d’autre qui ait du temps libre
C’est encore moins attirant qu’un modèle de copyright où toutes les contributions deviennent automatiquement la propriété d’un groupe donné
On pourrait soutenir que GitHub, tout en hébergeant du logiciel libre, est spécialisé dans la violation du copyright de ces logiciels, mais au moins, pour l’instant, il n’essaie pas de confisquer la propriété nominale des paquets
Puisqu’on parle de communauté, il ne s’agit pas d’une relation client-fournisseur, et la plupart des paquets ne sont importants qu’au point d’être fournis gratuitement
Cela pourrait finir en montants humiliants du genre « on vous donne 10 dollars par mois pour maintenir 100 000 paquets pour nous »
Internet regorge de code source non maintenu, simplement publié une fois tel quel. Personne n’a la garantie de recevoir des mises à jour pour du code gratuit
On a eu pas mal de chance que quelqu’un ait déjà forké yaml-rust pour créer yaml-rust2(https://github.com/Ethiraric/yaml-rust2/blob/master/document...)
C’est aussi appréciable que ce fork passe entièrement la suite de tests YAML et soit plus rapide dans les benchmarks. La migration semble également simple
Au fond, le problème demeure. Nous dépendons aujourd’hui du travail d’autres personnes qui acceptent de fournir gratuitement leur travail, mais cela ne durera peut-être pas éternellement
Je ne sais pas s’il existe une solution de contournement, à part les rémunérer pour leur temps et leurs efforts et espérer qu’elles continuent à faire du bon travail
“A pure rust YAML implementation.”
À l’inverse, serde_yaml était plus difficile à considérer comme du Rust pur, puisqu’il dépendait de unsafe-libyaml, une conversion de libyaml via c2rust
Sur un horizon temporel suffisamment long, la probabilité qu’un mainteneur open source abandonne un projet est de 1
La seule façon d’éviter cela est de faire des projets maintenus par une seule personne un tabou
Ce n’est probablement pas une option pour les projets Rust, parce que les personnes qui ont conçu la bibliothèque standard de Rust veulent qu’elle reste minimale
Mais c’est certainement possible lorsqu’on utilise d’autres langages dotés d’une bibliothèque standard suffisamment complète
Comme j’utilise volontairement des langages qui intègrent ce dont j’ai besoin, je n’utilise presque aucune bibliothèque externe en dehors des pilotes de base de données
Toute cette situation me paraît un peu absurde. Si le code fonctionne et que c’est le cas depuis des années, je ne vois pas en quoi le fait qu’il ne soit pas maintenu pose problème.
S’il n’a pas besoin d’être modifié et qu’on connaît ses limites et ses fonctionnalités, ça va.
Le code ne se détériore pas tout seul. Il m’est arrivé plusieurs fois d’emprunter ou d’intégrer du code vieux de plusieurs décennies et de l’utiliser sans problème.
Personnellement, je pense que j’ignorerais simplement toutes les plaintes concernant cette bibliothèque et que je passerais à autre chose.
Beaucoup de bugs se sont déjà accumulés et, même quand des correctifs existent, ils ne seront jamais appliqués. Il suffit de regarder https://github.com/chyh1990/yaml-rust/issues et /pulls.
Cela revient à prendre en charge la maintenance du morceau de code qu’on utilise.
Parfois, ça peut être mauvais : ça peut tourner au copier-coller ou à la duplication d’efforts.
Mais si un composant tiers n’est plus du tout maintenu, c’est assez compréhensible.
Oui, les dépendances peuvent être vendoriées. Pour les dépendances « presque terminées », dont le développement et la maintenance ont ralenti, c’est globalement ce qu’on fait depuis 20 ans.
Cela dit, je n’ai jamais travaillé dans un langage où les « batteries ne sont pas incluses ».
Y aurait-il un moyen de créer quelque chose comme
cargo vendor --aggressive, qui élaguerait tout le code mort des dépendances par rapport à ma crate ?Je me demande si cela pourrait rendre le problème du « revoir ses dépendances » plus gérable.
C’est un peu en marge du sujet principal de l’article, mais c’est lié au fait qu’au final, le choix des dépendances et toutes les responsabilités qui en découlent nous incombent.
Il semble y avoir de la place pour des outils qui nous aideraient à mieux assumer la responsabilité de ce qui est effectivement compilé dans une crate.
Sinon, je ne vois pas vraiment ce que ça améliore.