Les CDN publics sont inutiles et dangereux

Par Tim Perry de httptoolkit
18 minutes
Les CDN publics sont inutiles et dangereux

Il fut un temps où le chargement de scripts et de styles courants à partir d'un CDN public comme cdnjs ou les bibliothèques hébergées de Google était une "bonne pratique" - un excellent moyen d'accélérer instantanément le chargement de vos pages, d'optimiser la mise en cache et de réduire les coûts.

Aujourd'hui, c'est devenu une source de problèmes de sécurité, de confidentialité et de stabilité, avec des avantages quasi nuls. La semaine dernière, un chercheur en sécurité a montré comment les choses pouvaient mal tourner.

Il existe des moyens d'atténuer ces risques, mais en pratique, la meilleure solution consiste à les éviter complètement : hébergez vous-même votre contenu et vos dépendances, puis utilisez votre propre CDN de mise en cache directement devant votre application pour améliorer les performances.

Je vais vous expliquer ce que cela signifie dans une seconde. Mais d'abord, pourquoi était-ce une bonne idée, et comment est-ce devenu un tel gâchis ?

Pourquoi était-ce une bonne idée ?

Le principal avantage offert par les CDN publics de bibliothèques populaires était la mise en cache partagée. Si vous utilisiez une version populaire de jQuery, vous pouviez la référencer à partir d'une URL CDN publique, et si un utilisateur avait récemment visité un autre site qui utilisait la même version de jQuery à partir du même CDN, elle se chargerait instantanément, directement depuis son cache.

En fait, les sites peuvent partager des ressources (presque toujours JavaScript) entre eux pour améliorer la mise en cache, réduire les temps de chargement et économiser la bande passante pour les sites et les visiteurs.

Même dans le cas d'une mise en cache, cela présentait des avantages. Les navigateurs limitent le nombre de connexions ouvertes simultanées par domaine, ce qui limite les performances des téléchargements de ressources parallèles. En utilisant un domaine distinct pour certaines ressources, le chargement des ressources pouvait être réparti sur un plus grand nombre de connexions, améliorant ainsi les temps de chargement pour les visiteurs.

Enfin, les cookies du site principal ne sont pas envoyés dans les requêtes aux domaines tiers. Si vous avez de gros cookies stockés pour votre domaine, cela crée beaucoup de données inutiles envoyées dans chaque requête vers votre domaine, augmentant à nouveau inutilement l'utilisation de la bande passante et les temps de chargement (honnêtement, je ne suis pas sûr que cette surcharge ait vraiment un impact pratique, mais elle a certainement été largement documentée comme une importante "meilleure pratique web").

Ce sont là les raisons techniques abstraites. Il y avait aussi des raisons pratiques : principalement le fait que ces CDN offrent une bande passante gratuite et sont mieux équipés que vos serveurs pour la distribution de ressources statiques. Ils proposent de réduire considérablement vos coûts de bande passante, tout en étant mieux préparés à gérer les pics soudains de trafic, avec des serveurs plus largement répartis, ce qui rapproche votre contenu des utilisateurs finaux et réduit la latence. Qu'est-ce que vous n'aimez pas ?

Où est-ce que tout a mal tourné ?

C'est en tout cas l'idée. Malheureusement, depuis l'apogée de ce concept (vers 2016 environ), le web a radicalement changé.

Le plus important : le contenu en cache n'est plus partagé entre les domaines. C'est ce qu'on appelle le partitionnement du cache et c'est ce qui est proposé par défaut dans Chrome depuis octobre 2020 (v86), Firefox depuis janvier 2021 (v85) et Safari depuis 2013 (v6.1). Cela signifie que si un visiteur se rend sur le site A et le site B, et que tous deux chargent https://public-cdn.example/my-script.js, le script sera chargé à partir de zéro les deux fois.

Cela signifie que le principal avantage des CDN publics partagés n'est plus pertinent pour aucun des navigateurs modernes.

HTTP/2 a également bouleversé les autres avantages. En prenant en charge les flux parallèles au sein d'une seule connexion, les avantages de la répartition des ressources sur plusieurs domaines n'existent plus. HTTP/2 introduit également la compression des en-têtes, de sorte que la répétition d'un en-tête de cookie volumineux est extrêmement efficace, et toute surcharge éventuelle de ce fait n'est plus pertinente pour les performances non plus.

Cela annule complètement les avantages en termes de performances de l'utilisation d'un CDN public partagé (bien que les avantages en termes de coûts et de performances de l'utilisation des CDN en général demeurent - nous y reviendrons plus tard).

En plus de la disparition des avantages, de nombreux inconvénients majeurs sont apparus :

Problèmes de sécurité

Les risques de sécurité constituent le problème le plus important, mis en évidence par la divulgation, la semaine dernière, d'une faille de sécurité qui aurait permis à tout attaquant d'exécuter du code à distance dans cdnjs, ajoutant potentiellement du code malveillant aux bibliothèques JS utilisées par 12,7 % des sites sur Internet.

Il existe de nombreuses voies potentielles pour ce type d'attaque, et d'autres CDN comme unpkg.com ont présenté des vulnérabilités similaires par le passé.

Ce type de vulnérabilité constitue une menace pour l'ensemble de la sécurité sur le web. La possibilité d'injecter du JavaScript arbitraire directement dans un dixième du web permettrait des prises de contrôle de comptes triviaux, des vols de données et d'autres attaques à une échelle incroyablement catastrophique. Pour cette raison, les grands CDN publics comme ceux-ci sont des cibles énormes, offrant des récompenses et un impact potentiellement énormes sur l'ensemble du web en cas de violation unique.

Alors que les deux principales vulnérabilités CDN ci-dessus ont été découvertes par des chercheurs en sécurité et rapidement corrigées, les attaquants ont réussi à exploiter des scripts tiers partagés spécifiques ailleurs dans le passé, en injectant des scripts de minage de crypto dans des milliers de sites Web publics en une seule attaque.

Jusqu'à présent, il n'y a pas eu de prise de contrôle malveillante connue d'un CDN majeur de la même manière, mais il est impossible de savoir si les failles CDN ci-dessus ont été exploitées discrètement dans le passé, et il n'y a aucune garantie que la prochaine vague de vulnérabilités sera découverte par de bons samaritains non plus.

Problèmes de confidentialité

Les CDN publics présentent également des risques pour la vie privée. Alors que la vie privée en ligne était un sujet de niche lorsque les CDN publics sont devenus populaires, elle est aujourd'hui devenue un problème majeur pour le grand public et une préoccupation juridique sérieuse.

Cela peut être problématique pour l'utilisation des CDN publics, car le chargement des ressources d'une tierce partie entraîne une fuite d'informations : le fait que l'utilisateur charge cette ressource tierce pendant qu'il est sur votre site. Plus précisément, le domaine de votre site (et historiquement l'URL complète, mais généralement pas de nos jours) est envoyé dans l'en-tête Referer avec toutes les demandes de sous-ressources.

Au minimum, cela indique au CDN public qu'un utilisateur à l'adresse IP source est en train de visiter le site indiqué dans l'en-tête Referer. Dans certains cas, cela peut donner lieu à des fuites d'informations plus importantes : par exemple, si un script de fournisseur de paiement n'est chargé qu'au moment du paiement, les demandes de ressources tierces comme celle-ci fournissent suffisamment d'informations pour que ces CDN puissent identifier (par l'adresse IP et l'empreinte du navigateur) les utilisateurs qui effectuent des achats dans certains magasins.

Il s'agit potentiellement d'un grave problème de confidentialité, en particulier pour les CDN publics fournis par des entreprises qui ont un intérêt évident à suivre les utilisateurs, comme Google.

Problèmes de fiabilité

Les CDN ne sont pas infaillibles. Ils peuvent tomber complètement en panne, devenir inaccessibles, provoquer des erreurs inattendues, perdre du temps sous la charge ou même servir un contenu erroné.

Bien sûr, votre propre site web peut aussi être victime de ce genre de problème, tout comme n'importe quelle infrastructure alternative. Dans ces cas-là, vous disposez toutefois d'un recours : vous pouvez réparer votre site, contacter l'équipe d'assistance du service de CDN que vous payez ou utiliser un autre CDN devant votre serveur de contenu de manière transparente. Lorsque votre site de production dépend d'un CDN public gratuit, vous ne bénéficiez explicitement d'aucune garantie ou assistance formelle, et vous n'avez aucun contrôle sur le CDN.

C'est pire si vous vous inquiétez du long terme, car aucun CDN n'est éternel. Si vous disposez encore du code de votre application dans 20 ans, mais que les URL du CDN utilisé ont disparu, vous ne pourrez plus utiliser l'application sans avoir à déboguer et à rechercher d'anciennes copies des fichiers de la bibliothèque. De même, mais de façon plus immédiate : si vous êtes dans un avion, vous ne pouvez pas accéder à votre CDN, ce qui rend impossible tout développement rapide hors ligne.

L'utilisation d'un CDN public ajoute un point de défaillance unique supplémentaire à votre site. Maintenant, si vos serveurs tombent en panne ou si les serveurs du CDN public sont inaccessibles, tout est cassé. Toutes choses égales par ailleurs, il est préférable de garder le cercle des dépendances critiques restreint.

Limitations des performances

Par défaut, les CDN publics chargent chaque ressource comme un fichier séparé, sans regroupement. Bien que HTTP/2 réduise le besoin de regroupement, cela reste sous-optimal pour les applications web non triviales, pour deux raisons :

  • Une compression moins bonne : La compression des réponses HTTP est toujours appliquée par réponse. En répartissant votre script sur plusieurs réponses, au lieu de le compresser en une seule fois, les performances de compression sont réduites. Cela est particulièrement vrai pour le contenu facilement compressible qui partage probablement beaucoup de contenu commun - c'est-à-dire les fichiers JavaScript.
  • Pas question de secouer les arbres : un CDN public doit vous envoyer à chaque fois l'intégralité de la bibliothèque JavaScript dans votre réponse. En revanche, les bundles modernes peuvent détecter intelligemment si des parties de scripts importés sont utilisées par le biais du tree shaking au moment de la construction, et n'inclure que ces parties dans le code de votre application, ce qui peut réduire considérablement la taille totale de vos dépendances d'exécution.

Il s'agit d'une question compliquée - il y aura des moments où ce qui précède ne s'applique pas, et il est important de mesurer la réalité pour votre application spécifique. Cela dit, le regroupement des dépendances sera plus rapide dans 90 % des cas et constitue une solution par défaut raisonnable si vous construisez quelque chose d'important.

Que devriez-vous faire à la place ?

Hébergez vos propres dépendances, placez un cache directement devant votre application et rendez votre application résiliente aux ressources manquantes.

En hébergeant vos propres dépendances, vous contrôlez tout ce dont votre application a besoin et vous n'avez pas à dépendre d'une infrastructure publique. En utilisant un cache directement devant votre site, vous bénéficiez des mêmes avantages de mise en cache, de distribution de contenu et de performance que les CDN publics, tout en conservant des mesures d'atténuation pour les éventuels inconvénients.

Lorsque je parle de caches, je suggère principalement un service payant de proxy inverse de mise en cache, comme Cloudflare, Fastly, Cloudfront, Akamai, etc. (bien que ces services soient payants, la plupart ont des niveaux gratuits généreux qui vous permettent de démarrer ou d'héberger de petits sites). En plus de la mise en cache, chacun d'entre eux offre diverses fonctionnalités, comme des protections contre les DDoS, des travailleurs en périphérie sans serveur, des analyses côté serveur, une optimisation automatique du contenu, etc.

Il est également possible d'exécuter votre propre proxy inverse de mise en cache, bien sûr, mais à moins de le distribuer dans des centaines de centres de données à l'échelle mondiale, vous perdez une grande partie de l'avantage en termes de performances en procédant ainsi, et cela risque d'être plus coûteux dans la pratique.

Étant donné que cette infrastructure de mise en cache peut mettre en cache n'importe quoi, le contenu lui-même et la plupart de la configuration de la mise en cache (sous la forme d'en-têtes Cache-Control) étant définis par votre serveur dorsal, il est également beaucoup plus facile de migrer entre eux si vous rencontrez des problèmes. Si un CDN hébergé publiquement tombe en panne, vous devez trouver une URL de remplacement qui sert exactement le même contenu, changer cette URL partout dans votre code et redéployer l'ensemble de votre application.

En revanche, si votre proxy inverse de mise en cache tombe en panne, vous avez la possibilité de placer immédiatement un autre service de mise en cache devant votre site, ou de servir temporairement du contenu statique directement à partir de vos serveurs, et de rétablir le fonctionnement sans modifier le code ni déployer le backend. Votre contenu et la mise en cache restent sous votre contrôle.

Même si tout cela est bien, il est toujours judicieux de s'assurer que votre front-end est résilient aux ressources qui ne se chargent pas. De nombreuses dépendances de script ne sont pas strictement nécessaires au fonctionnement de votre page Web. Même sans les défaillances des CDN, il arrive que les scripts ne se chargent pas en raison de mauvaises connexions, d'extensions de navigateur, de la désactivation de JavaScript par les utilisateurs ou de fluctuations quantiques du continuum espace-temps. Il est préférable que cela n'entraîne aucun problème pour vos utilisateurs.

Cela n'est pas possible dans tous les cas, surtout dans les applications web complexes, mais en minimisant les dépendances matérielles de votre contenu et en évitant de bloquer les sous-ressources qui ne sont pas strictement nécessaires, vous améliorerez la résilience et les performances de votre application pour tout le monde (chargement asynchrone des scripts, rendu côté serveur et amélioration progressive sont vos amis ici).

Tout bien considéré, avec des outils et des services modernes, cette approche peut être incroyablement efficace. Troy Hunt a écrit une exploration détaillée du fonctionnement de la mise en cache pour son populaire site Pwned Passwords. Dans son cas :

  • 477,6 Go de sous-ressources sont servis depuis son domaine chaque semaine.
  • Sur ce total, 476,7 Go proviennent du cache (taux de cache de 99,8 %).
  • Le site reçoit également 32,4 millions de requêtes à son API par semaine.
  • 32,3 millions de ces requêtes sont servies à partir du cache (taux de cache de 99,6 %).
  • Les autres points d'accès à l'API sont gérés par les fonctions sans serveur d'Azure.

Au total, les coûts d'hébergement de ce site - qui gère des millions de vérifications quotidiennes de mots de passe - s'élèvent à environ 3 cents par jour, la grande majorité des économies réalisées par rapport aux architectures traditionnelles provenant de la mise en cache du contenu statique et des requêtes API.

L'exécution du code sur les serveurs est lente et coûteuse, tandis que la mise en cache du contenu peut être extrêmement économique et performante, sans nécessiter de CDN publics.

Intégrité des sous-ressources

L'intégrité des sous-ressources (SRI) est souvent mentionnée dans ces discussions. L'ISR tente de résoudre certains des problèmes liés aux CDN publics sans les éliminer complètement, en validant le contenu du CDN par rapport à un hachage fixe pour vérifier que vous obtenez le bon contenu.

C'est mieux que rien, mais c'est toujours moins efficace que d'héberger et de mettre en cache son propre contenu, pour plusieurs raisons :

  • SRI bloquera l'injection de code malveillant par un CDN, mais si c'est le cas, votre site ne pourra tout simplement pas charger cette ressource, ce qui pourrait le casser entièrement, ce qui n'est pas vraiment un bon résultat.
  • Vous devez constamment maintenir le hachage SRI pour qu'il corresponde à toute modification ou mise à jour de version future, ce qui crée un tout nouveau moyen de casser complètement votre page Web.
  • Cela ne protège pas contre les risques de confidentialité des ressources tierces, et cela aggrave les risques de fiabilité, au lieu de les améliorer, en introduisant de nouvelles façons dont le chargement des ressources peut échouer.
  • Dans de nombreuses organisations, si d'importants contrôles SRI commençaient à échouer et à casser la fonctionnalité en production, la réaction la plus probable à la panne qui en résulterait serait de supprimer le hachage SRI pour résoudre l'erreur immédiate, ce qui irait à l'encontre de toute la configuration.

Cela ne veut pas dire que SRI est inutile. Si vous devez charger une ressource provenant d'une tierce partie, la validation de son contenu est certainement une protection précieuse ! Cependant, dans la mesure du possible, il est strictement préférable de ne pas dépendre de ressources tierces au moment de l'exécution, et il est généralement facile et bon marché de le faire.

Futur hypothétique

Une dernière brève tangente pour terminer : une technologie que je vois très prometteuse à l'avenir est IPFS. IPFS offre un aperçu d'un monde possible d'hébergement adressé au contenu, où :

  • Tout le contenu est distribué mondialement, bien plus que n'importe quel CDN aujourd'hui, de manière complètement automatique.
  • Il n'y a pas de service unique qui puisse tomber en panne et mettre hors service de grandes parties de l'internet (comme Fastly l'a fait le mois dernier).
  • Chaque élément de contenu est chargé de manière totalement indépendante, sans référence à la ressource de référence et récupéré à partir d'hôtes différents, ce qui rend le suivi difficile.
  • Le contenu est défini par son hachage, ce qui permet d'intégrer l'ISR dans le protocole lui-même et de garantir l'intégrité du contenu à tout moment.

IPFS est encore très récent, donc rien de tout cela n'est vraiment pratique aujourd'hui pour les applications de production, et il aura ses propres problèmes qui n'apparaîtront que lorsqu'il commencera à être utilisé dans le monde réel.

Néanmoins, s'il mûrit et se généralise, il pourrait vraisemblablement devenir une bien meilleure solution pour la distribution de contenu statique que toutes les options actuelles, et je suis enthousiaste quant à son avenir.

Avertissements

Ok, ok, ok, le titre est un peu sensationnaliste, d'accord. Je dois admettre que je pense qu'il y a un petit cas limite où les CDN publics sont utiles : le prototypage. La possibilité de déposer une URL et de tester immédiatement quelque chose est intéressante et précieuse, et peut être très utile pour les petites démonstrations de codage, etc.

Je ne veux pas non plus dire qu'il s'agit d'une règle absolue ou que tous les sites utilisant un CDN public sont des désastres. Il est difficile d'exclure toutes les ressources tierces lorsqu'elles constituent un canal de distribution officiel pour une bibliothèque ou lorsqu'elles sont chargées par des plugins ou autres. Je suis moi-même coupable d'en avoir inclus quelques-unes dans cette page, bien que je m'efforce de supprimer ces derniers scripts en ce moment même.

Parfois, c'est plus facile à dire qu'à faire, mais je crois fermement que l'objectif d'éviter autant que possible les CDN publics dans les applications de production est un objectif valable. Dans tout environnement où vous prenez le développement suffisamment au sérieux pour qu'il mérite d'être débattu, il vaut la peine de prendre le temps d'héberger votre propre contenu.

Conclusion

La mise en cache est difficile, la création de sites Web à forte visibilité est compliquée, et la quantité d'utilisateurs et de sources potentielles de pics de trafic sur Internet aujourd'hui rend tout cela difficile.

Il est clair que vous souhaitez externaliser le plus possible ce travail difficile. Cependant, le web a évolué au fil des ans et les CDN publics ne sont plus la bonne solution.

Les avantages des CDN publics ne sont plus pertinents, et leurs inconvénients sont importants, tant pour les sites individuels que pour la sécurité du web dans son ensemble. Il est préférable de les éviter dans la mesure du possible, principalement en hébergeant soi-même son contenu avec un reverse-proxy de mise en cache en amont, afin de construire facilement et à moindre coût des applications web performantes.

Vous avez des idées, des commentaires ou des exemples de bonus pour l'un des points ci-dessus ? Contactez-moi par e-mail ou sur Twitter et faites-le moi savoir.

Vous voulez tester ou déboguer les requêtes HTTP, la mise en cache et les erreurs ? Interceptez, inspectez et simulez HTTP(S) de n'importe où à n'importe quel endroit avec HTTP Toolkit.

Auteur : Tim Perry sur httptoolkit