Ajouter des en-têtes de sécurité avec Lambda@Edge et Terraform dans AWS S3/CloudFront
Publié: 2021-04-24Lorsque vous vous connectez à https://app.sendgrid.com pour consulter les détails de votre compte ou visitez https://mc.sendgrid.com pour les campagnes marketing, vous visitez nos applications Web frontales hébergées dans des compartiments AWS S3 avec Distributions CloudFront au-dessus d'eux.
Nos applications Web se composent essentiellement de fichiers JavaScript et CSS minifiés et fractionnés en code, de fichiers HTML et de fichiers image chargés dans des compartiments S3 en tant que caches CloudFront et les transmettent à nos utilisateurs. Chacun des environnements de nos applications Web, qu'il s'agisse de test, de préproduction ou de production, dispose d'un compartiment S3 et d'une distribution CloudFront distincts.
Cette infrastructure AWS S3 et CloudFront fonctionne bien pour nos applications Web à grande échelle dans l'hébergement de fichiers sur un réseau de diffusion de contenu, mais nos configurations initiales manquaient de protections plus strictes sous la forme d'en-têtes de sécurité.
L'ajout de ces en-têtes de sécurité empêcherait les utilisateurs d'attaques, telles que les scripts intersites, le reniflage MIME, le détournement de clic, l'injection de code et les attaques de l'homme du milieu liées à des protocoles non sécurisés. Si rien n'était fait, cela aurait de graves conséquences pour les données de nos clients et pour la confiance de notre entreprise dans sa capacité à offrir une expérience sécurisée sur le Web.
Avant de chercher comment ajouter ces en-têtes, nous avons d'abord pris du recul pour voir où nous en étions. Après avoir exécuté l'URL de notre application Web via un site Web d'analyse des en-têtes de sécurité , nous avons sans surprise reçu une note d'échec, mais nous avons vu une liste utile d'en-têtes à examiner, comme indiqué ci-dessous.
Comme vous pouvez le voir, il y avait beaucoup de place à l'amélioration. Nous avons recherché comment configurer nos ressources AWS S3 et CloudFront pour répondre avec des en-têtes de sécurité afin d'atténuer les risques et les vulnérabilités mentionnés.
À un niveau élevé, nous pouvons y parvenir en créant une fonction Lambda@Edge qui modifie les en-têtes de réponse d'origine pour ajouter les en-têtes de sécurité souhaités avant que les fichiers de l'application Web ne reviennent au navigateur de l'utilisateur.
La stratégie consiste à tester d'abord le branchement manuel des éléments via la console AWS. Ensuite, nous mettrons ces configurations dans Terraform pour enregistrer cette partie de l'infrastructure dans le code pour une référence future et un partage entre d'autres équipes et applications.
Quel type d'en-têtes de sécurité aimerions-nous ajouter ?
Dans le cadre des recommandations de notre équipe Product Security, nous avons été chargés d'ajouter des en-têtes de sécurité tels que "Strict-Transport-Security" et "X-Frame-Options". Nous vous recommandons également de consulter des ressources telles que la feuille de triche de sécurité Web MDN pour vous mettre au courant. Voici un bref résumé des en-têtes de sécurité que vous pouvez appliquer à vos applications Web.
Strict-Transport-Security (HSTS)
Il s'agit de fournir des conseils au navigateur pour accéder à votre application Web via HTTPS plutôt que HTTP.
Politique de sécurité du contenu (CSP)
Il s'agit de définir des listes d'autorisation explicites sur le type de ressources que vous chargez ou auxquelles vous vous connectez dans votre application Web, telles que les scripts, les images, les styles, les polices, les requêtes réseau et les iframes. C'était le plus difficile à configurer pour nous, car nous avions des scripts, des images, des styles et des points de terminaison d'API tiers à enregistrer explicitement dans ces politiques.
Conseil : utilisez l' en-tête Content-Security-Policy-Report-Only pour vous aider à tester dans certains environnements. Si certaines ressources violaient les politiques, nous avons observé une sortie de console utile des ressources que nous devions autoriser dans nos politiques.
Si vous souhaitez éviter un drôle d'incident avec des écrans vides et des applications Web qui ne se chargent pas, nous vous recommandons fortement d'expérimenter d'abord vos politiques en mode rapport uniquement et de faire des tests approfondis avant de vous sentir suffisamment en confiance pour déployer ces politiques de sécurité en production.
X-Content-Type-Options
Il s'agit de maintenir et de charger les actifs avec les types MIME corrects dans votre page Web.
X-Frame-Options
Il s'agit de fournir des règles sur la façon dont votre application Web se charge potentiellement dans un iframe.
X-XSS-Protection
Cela empêche le chargement des pages si une attaque de script intersite est détectable dans certains navigateurs.
Politique de référence
Cela gère la façon dont l'en-tête "Referrer" avec des informations sur l'origine de la demande est transmise lorsque vous suivez des liens vers des sites ou des ressources externes.
Avec ces en-têtes de sécurité à l'esprit, revenons à la façon dont nous avons configuré nos distributions CloudFront aujourd'hui et comment les fonctions Lambda@Edge nous aideront à atteindre notre objectif.
Utilisation de Lambda@Edge avec nos distributions CloudFront
Pour nos distributions CloudFront, nous configurons des éléments tels que :
- Les certificats SSL pour les domaines à attacher au-dessus de l'URL CloudFront comme https://app.sendgrid.com
- Origines du compartiment S3
- Groupes d'origine avec compartiments principaux et répliques pour un basculement automatique
- Comportements du cache
Ces comportements de cache, en particulier, nous permettent de contrôler la durée pendant laquelle nous voulons que les réponses pour certains types de chemins et de fichiers soient mises en cache dans les serveurs périphériques du monde entier. De plus, les comportements de cache nous fournissent également un moyen de déclencher les fonctions AWS Lambda en réponse aux divers événements, tels que les demandes d'origine et les réponses d'origine. Vous pouvez considérer les fonctions AWS Lambda comme un code spécifique que vous définissez et qui s'exécutera en réponse à un certain événement.
Dans notre cas, nous pouvons modifier les en-têtes de réponse d'origine avant qu'ils ne soient mis en cache par les serveurs périphériques. Notre fonction Lambda@Edge ajoutera des en-têtes de sécurité personnalisés à la réponse d'origine avant qu'elle ne retourne finalement au serveur périphérique et avant que l'utilisateur final ne reçoive les fichiers JavaScript, CSS et HTML avec ces en-têtes.
Nous avons modélisé notre approche d'après ce billet de blog AWS et l'avons étendue pour faciliter la modification d'une politique de sécurité de contenu spécifique. Vous pouvez modéliser votre fonction Lambda@Edge d'après la façon dont nous configurons les choses en générant des listes pour le script, le style et les sources de connexion. Cette fonction modifie efficacement les en-têtes de réponse d'origine CloudFront et ajoute chaque en-tête de sécurité avec certaines valeurs à la réponse avant de revenir en appelant la fonction de rappel fournie comme indiqué ci-dessous.
Comment testons-nous cette fonction Lambda@Edge ?
Avant de modifier officiellement la façon dont vos ressources sont renvoyées avec les en-têtes de sécurité, vous devez vérifier que la fonction fonctionne après avoir tout configuré manuellement via la console AWS. Il est crucial que vos applications Web puissent se charger et fonctionner correctement avec les en-têtes de sécurité ajoutés à vos réponses réseau. La dernière chose que vous voulez entendre est une panne inattendue due aux en-têtes de sécurité, alors testez-les soigneusement dans vos environnements de développement.
Il est également important de savoir exactement ce que vous allez écrire dans le code Terraform plus tard pour enregistrer cette configuration dans votre base de code. Si vous ne connaissez pas Terraform, il vous offre un moyen d'écrire et de gérer votre infrastructure cloud via du code.
Conseil : consultez la documentation Terraform pour voir si elle peut vous aider à maintenir vos configurations complexes sans avoir à vous souvenir de toutes les étapes que vous avez effectuées dans les consoles cloud.
Comment démarrer dans la console AWS
Commençons par configurer les éléments manuellement via la console AWS.
- Tout d'abord, vous devez créer la fonction Lambda@Edge dans la région "us-east-1". En accédant à la page des services Lambda, nous cliquons sur "Créer une fonction" et nommons-la quelque chose comme "testSecurityHeaders1".
2. Vous pouvez utiliser un rôle existant avec des autorisations pour exécuter la fonction sur des serveurs périphériques ou vous pouvez utiliser l'un de leurs modèles de stratégie de rôle, tels que « Basic Lambda@Edge Permissions… », et nommez-le « lambdaedgeroletest ».
3. Après avoir créé votre fonction et votre rôle Lambda de test, vous devriez voir quelque chose comme ceci où vous remarquerez le bouton « Ajouter un déclencheur » pour la fonction. C'est là que vous associerez éventuellement Lambda au comportement de cache d'une distribution CloudFront déclenché sur l'événement de réponse d'origine.
4. Ensuite, vous devez modifier le code de fonction avec le code d'en-têtes de sécurité que nous avons créé auparavant et cliquer sur "Enregistrer".
5. Après avoir enregistré le code de la fonction, testons si votre fonction Lambda fonctionne même en faisant défiler vers le haut et en appuyant sur le bouton "Tester". Vous allez créer un événement de test nommé « samplecloudfrontresponse » à l'aide du modèle d'événement « cloudfront-modify-response-header » pour simuler un événement de réponse d'origine CloudFront réel et pour voir comment votre fonction s'exécute par rapport à celui-ci.
Vous remarquerez des choses comme l'objet d'en-tête « cf.response » que votre code de fonction Lambda modifiera.
6. Après avoir créé l'événement de test, vous cliquerez à nouveau sur le bouton « Tester » et vous devriez voir comment la fonction Lambda s'est exécutée par rapport à celui-ci. Il devrait s'exécuter avec succès avec des journaux affichant la réponse résultante avec des en-têtes de sécurité supplémentaires comme celui-ci.
Génial, la fonction Lambda semble avoir ajouté correctement les en-têtes de sécurité à la réponse !
7. Remontons dans la zone « Concepteur » et cliquez sur le bouton « Ajouter un déclencheur » afin que vous puissiez associer la fonction Lambda aux comportements de cache de votre distribution CloudFront sur l'événement de réponse d'origine. Assurez-vous de sélectionner un déclencheur "CloudFront" et cliquez sur le bouton "Déployer sur Lambda@Edge".
8. Ensuite, sélectionnez la distribution CloudFront (dans notre exemple, nous avons effacé l'entrée ici pour des raisons de sécurité) et un comportement de cache à lui associer.
Vous choisissez ensuite le comportement de cache « * » et sélectionnez l'événement « Réponse d'origine » pour qu'il corresponde à tous les chemins de requête vers votre distribution CloudFront et pour vous assurer que la fonction Lambda s'exécute toujours pour toutes les réponses d'origine.
Vous cochez ensuite l'accusé de réception avant de cliquer sur « Déployer » pour déployer officiellement votre fonction Lambda.
9. Après avoir réussi à associer votre fonction Lambda à tous les comportements de cache de votre distribution CloudFront, vous devriez voir quelque chose de similaire dans la zone « Designer » du tableau de bord Lambda où vous pouvez voir les déclencheurs CloudFront et avoir la possibilité de les afficher ou de les supprimer.
Apporter des modifications à votre code Lambda
Chaque fois que vous devrez apporter des modifications à votre code Lambda, nous vous recommandons :
- Publiez une nouvelle version via le menu déroulant du bouton "Actions"
- Supprimez les déclencheurs sur l'ancienne version (vous pouvez cliquer sur le menu déroulant "Qualifiers" pour voir toutes les versions de votre Lambda)
- Associez les déclencheurs au numéro de version le plus récent que vous avez récemment publié
Lors du déploiement de votre Lambda pour la première fois ou après la publication d'une nouvelle version de votre Lambda et l'association des déclencheurs à la nouvelle version de Lambda, il se peut que vous ne voyiez pas immédiatement les en-têtes de sécurité dans vos réponses pour votre application Web. Cela est dû à la manière dont les serveurs périphériques de CloudFront mettent en cache les réponses. En fonction de la durée de vie définie dans vos comportements de cache, vous devrez peut-être attendre un certain temps pour voir les nouveaux en-têtes de sécurité, sauf si vous effectuez une invalidation du cache dans votre distribution CloudFront concernée.
Après avoir redéployé vos modifications sur votre fonction Lambda, il faut souvent un certain temps pour que le cache soit vidé (en fonction de vos paramètres de cache CloudFront) avant que vos réponses n'aient les dernières modifications apportées à vos en-têtes de sécurité.
Astuce : pour éviter de trop rafraîchir la page ou de ne pas savoir si vos modifications ont fonctionné, lancez une invalidation du cache CloudFront pour accélérer le processus de vidage du cache afin que vous puissiez voir vos en-têtes de sécurité mis à jour.
Accédez à votre page Services CloudFront, attendez que le statut de votre distribution CloudFront soit déployé, ce qui signifie que toutes les associations lambda sont terminées et déployées, et accédez à l'onglet « Invalidations ». Cliquez sur "Créer une invalidation" et mettez "/*" comme chemin d'objet pour invalider toutes les choses dans le cache et cliquez sur "Invalider". Cela ne devrait pas prendre trop de temps, et une fois terminé, l'actualisation de votre application Web devrait voir les dernières modifications de l'en-tête de sécurité.
Lorsque vous itérez sur vos en-têtes de sécurité en fonction de ce que vous trouvez comme violations ou erreurs dans votre application Web, vous pouvez répéter ce processus :
- Publication d'une nouvelle version de la fonction Lambda
- Suppression des déclencheurs sur l'ancienne version de Lambda
- Associer les triggers sur la nouvelle version
- Cache invalidant votre distribution CloudFront
- Tester votre application Web
- Répéter jusqu'à ce que vous vous sentiez confiant et sûr que les choses fonctionnent comme prévu sans pages vierges, demandes d'API échouées ou erreurs de sécurité de la console
Une fois que les choses sont stables, vous pouvez éventuellement passer à Terraforming ce que vous venez de faire manuellement dans les configurations de code, en supposant que Terraform est intégré à vos comptes AWS. Nous n'expliquerons pas comment configurer Terraform depuis le début, mais nous vous montrerons des extraits de ce à quoi ressemblera le code Terraform.
Terraformation de Lambda@Edge déclenchée par notre distribution CloudFront
Après avoir itéré sur la fonction Lambda@Edge pour les en-têtes de sécurité dans la région "us-east-1", nous voulions l'ajouter à notre base de code Terraform pour la maintenabilité du code et le contrôle de version sur la route.
Pour tous les comportements de cache que nous avons déjà implémentés, nous avons dû associer le comportement de cache à la fonction Lambda@Edge, déclenchée par l'événement de réponse d'origine.
Les étapes suivantes supposent que vous avez déjà la plupart des distributions CloudFront et des compartiments S3 configurés via Terraform. Nous allons nous concentrer sur les principaux modules et propriétés liés à Lambda@Edge et ajouter le déclencheur aux comportements de cache de la distribution CloudFront. Nous n'expliquerons pas comment configurer vos compartiments S3 et d'autres paramètres de distribution CloudFront à partir de zéro via Terraform, mais nous espérons que vous pourrez voir le niveau d'effort pour y parvenir par vous-même.
Nous divisons actuellement nos ressources AWS en dossiers de modules séparés et transmettons des variables dans ces modules pour plus de flexibilité dans notre configuration. Nous avons un dossier d' apply
avec un sous-dossier de development
et production
et chacun a son propre fichier main.tf
où nous appelons ces modules avec certaines variables d'entrée pour instancier ou modifier nos ressources AWS.
Ces sous-dossiers ont également chacun un dossier lambdas
dans lequel nous conservons notre code Lambda, tel qu'un fichier security_headers_lambda.js
. Le security_headers_lambda.js
a le même code que nous avons utilisé dans notre fonction Lambda lorsque nous avons testé manuellement, sauf que nous l'enregistrons également dans notre base de code pour que nous puissions le compresser et le télécharger via Terraform.
1. Tout d'abord, nous avons besoin d'un module réutilisable pour compresser notre fichier Lambda avant qu'il ne soit téléchargé et publié comme une autre version de notre fonction Lambda@Edge. Cela prend un chemin vers notre dossier Lambda contenant l'éventuelle fonction Lambda Node.js.
2. Ensuite, nous ajoutons à notre module CloudFront existant, qui encapsule les compartiments S3, les politiques et les ressources de distribution CloudFront en créant également une ressource Lambda construite à partir du fichier Lambda compressé. Étant donné que les sorties du module zip Lambda passent pour des variables dans le module CloudFront pour configurer la ressource Lambda, nous devons spécifier la région du fournisseur AWS comme « us-east-1 » et avec une stratégie de rôle de travail comme celle-ci.
3. Dans le module CloudFront, nous associons ensuite cette fonction Lambda@Edge aux comportements de cache de la distribution CloudFront, comme illustré ci-dessous.
4. Enfin, en rassemblant le tout dans le fichier main.tf
de notre dossier apply/development
ou apply/production
, nous appelons tous ces modules et insérons les sorties appropriées en tant que variables dans notre module CloudFront, comme illustré ici.
Ces ajustements de configuration prennent essentiellement en charge les étapes manuelles que nous avons effectuées dans la section précédente pour mettre à jour le code Lambda et associer la nouvelle version aux comportements de cache et aux déclencheurs de CloudFront pour les événements de réponse d'origine. Woohoo ! Inutile de parcourir ou de mémoriser les étapes de la console AWS tant que nous appliquons ces modifications à nos ressources.
Comment déployer cela en toute sécurité dans différents environnements ?
Lorsque nous avons associé pour la première fois notre fonction Lambda@Edge à notre distribution de test CloudFront, nous avons rapidement remarqué que notre application Web ne se chargeait plus correctement. Cela était principalement dû à la façon dont nous avons implémenté notre en-tête Content-Security-Policy et au fait qu'il ne couvrait pas toutes les ressources que nous avons chargées dans notre application. Les autres en-têtes de sécurité présentaient moins de risques en termes de blocage du chargement de notre application. À l'avenir, nous nous concentrerons sur le déploiement des en-têtes de sécurité avec d'autres itérations à l'esprit pour affiner l'en-tête Content-Security-Policy.
Comme mentionné précédemment, nous avons découvert comment tirer parti de l' en-tête Content-Security-Policy-Report-Only pour minimiser les risques lorsque nous rassemblons davantage de domaines de ressources à ajouter dans chacune de nos politiques.
Dans ce mode de rapport uniquement, les stratégies continueront de s'exécuter dans le navigateur et afficheront les messages d'erreur de la console concernant toute violation des stratégies. Cependant, cela ne bloquera pas purement et simplement ces scripts et ces sources, de sorte que notre application Web peut toujours fonctionner comme d'habitude. Il nous appartient de continuer à parcourir l'ensemble de l'application Web pour nous assurer de ne manquer aucune source importante dans nos politiques, sinon cela affectera négativement nos clients et notre équipe d'assistance.
Pour chaque environnement, vous pouvez déployer les en-têtes de sécurité Lambda comme suit :
- Publiez les modifications apportées à votre Lambda manuellement ou via un plan Terraform et appliquez les modifications à l'environnement avec d'autres en-têtes de sécurité et l'en-tête Content-Security-Policy-Report-Only en premier.
- Attendez que l'état de votre distribution CloudFront soit entièrement déployé avec le Lambda associé aux comportements de cache.
- Effectuez une invalidation du cache sur votre distribution CloudFront si les en-têtes de sécurité précédents s'affichent toujours ou si vos modifications actuelles mettent trop de temps à s'afficher dans votre navigateur.
- Visitez et parcourez les pages de votre application Web avec les outils de développement ouverts, en analysant la console à la recherche de tout message d'erreur de console « Rapport uniquement… » pour améliorer votre en-tête Content-Security-Policy.
- Apportez des modifications à votre code Lambda pour prendre en compte ces violations signalées.
- Répétez à partir de la première étape jusqu'à ce que vous vous sentiez suffisamment en confiance pour changer votre en-tête de Content-Security-Policy-Report-Only à Content-Security-Policy, ce qui signifie que l'environnement l'appliquera.
Amélioration de notre score d'en-têtes de sécurité
Après avoir appliqué avec succès les modifications Terraform à nos environnements et invalidé les caches CloudFront, nous avons actualisé les pages de notre application Web. Nous avons gardé les outils de développement ouverts pour voir les en-têtes de sécurité, tels que HSTS, CSP et d'autres dans nos réponses réseau, tels que les en-têtes de sécurité indiqués ci-dessous.
Nous avons également exécuté notre application Web via un rapport d'analyse des en-têtes de sécurité tel que celui de ce site . En conséquence, nous avons été témoins de grandes améliorations (une note A !) par rapport à une note précédemment défaillante, et vous pouvez obtenir des améliorations similaires après avoir modifié vos configurations S3/CloudFront pour avoir des en-têtes de sécurité en place.
Aller de l'avant avec les en-têtes de sécurité
Après avoir configuré manuellement les en-têtes de sécurité via la console AWS ou réussi à terraformer la solution et appliqué les modifications à chacun de vos environnements, vous disposez désormais d'une excellente base pour itérer davantage et améliorer vos en-têtes de sécurité existants à l'avenir.
En fonction de l'évolution de votre application web, vous devrez peut-être rendre votre en-tête Content-Security-Policy plus précis en termes de ressources autorisées pour une sécurité renforcée. Ou, vous devrez peut-être ajouter un nouvel en-tête entièrement à des fins distinctes ou pour combler une autre faille de sécurité.
Avec ces futures modifications apportées à vos en-têtes de sécurité dans vos fonctions Lambda@Edge, vous pouvez suivre des stratégies de publication similaires par environnement pour vous assurer que vos applications Web sont protégées contre les attaques malveillantes sur le Web et fonctionnent toujours sans que vos utilisateurs remarquent la différence.
Pour plus d'articles écrits par Alfred Lucero, rendez-vous sur sa page d'auteur de blog : https://sendgrid.com/blog/author/alfred/