Déployer un backend TypeScript full-serverless avec SAM (3/3)

Partie 3 - Sécurité s’il vous plaît

Cette série de trois articles a pour objectif de présenter l’usage de blueprints pour déployer un backend full-serverless développé avec typescript. Cet article, le dernier de la série, aura pour but d’expliquer les best practices liées à la sécurité d’un tel backend avec AWS. Ces best practices étant mises en place grâce à ce blueprint.

Pourquoi sécuriser ses ressources publiques?

Un des aspects les plus importants de ce blueprint est la sécurité : en effet, l’architecture contient un point d’entrée public qui est l’API, il est donc nécessaire de le sécuriser.

Cette sécurité est mise en place à 2 niveaux : un contrôle d’accès utilisateur grâce à Cognito et un pare-feu WAF qui bloque les requêtes venant d’une source autre que le CloudFront.

Pour rappel, ceci est l’architecture finale que nous allons mettre en place :

Cet article abordera donc l’utilisation de Amazon Cognito, ainsi que du Secrets Manager et du WAF. Il est important de comprendre pourquoi il est essentiel de mettre ces ressources en place.

Pourquoi se servir de Cognito avec son API?

Amazon Cognito est un service simple de synchronisation des données et de l'identité des utilisateurs qui nous aide à gérer et à synchroniser en toute sécurité les données des applications pour nos utilisateurs sur leurs appareils mobiles.

Il nous permet de créer des identités uniques pour vos utilisateurs par l'intermédiaire de plusieurs fournisseurs de connexion publics (Amazon, Facebook et Google) et de prendre en charge les invités non authentifiés. Grâce à Cognito nous pouvons sauvegarder les données des applications localement sur les appareils des utilisateurs, ce qui permet aux applications de fonctionner même lorsque les appareils sont hors ligne.

Nous nous servirons ici de Cognito comme d’un authentificateur auprès de l’API pour autoriser (ou non) certains utilisateurs à accéder à certaines ressources de l’API.

Pourquoi mettre un CloudFront devant son API?

Outre les nombreux avantages de se servir d’une distribution CloudFront qui sont listés ici, nous parlerons des avantages liés à la sécurité qu’apportent CloudFront à notre infrastructure.

Protection contre les attaques des couches réseau et application

Amazon CloudFront, AWS Shield, AWS Web Application Firewall (WAF) et Amazon Route 53 fonctionnent conjointement en toute transparence afin de créer un périmètre de sécurité flexible et à couches contre les différents types d'attaques, dont les attaques DDoS contre les couches réseau et application. Tous ces services sont corésidents à la périphérie d'AWS et fournissent un périmètre de sécurité extensible, fiable et aux performances élevées pour nos applications et contenus. Avec CloudFront comme «porte d'entrée» de notre application et infrastructure, nous retirons la surface d'attaque principale de nos contenus, données, codes et infrastructures critiques.

Chiffrements SSL/TLS et HTTPS

Avec Amazon CloudFront, nous pouvons diffuser notre contenu, les API ou les applications via SSL/TLS et les fonctionnalités SSL avancées sont activées automatiquement. Nous pouvons utiliser AWS Certificate Manager (ACM) afin de créer facilement un certificat SSL personnalisé et de déployer notre distribution CloudFront gratuitement. ACM gère automatiquement le renouvellement des certificats, en éliminant les frais généraux et les coûts associés à un processus de renouvellement manuel. De plus, CloudFront fournit un certain nombre d'optimisations et de capacités avancées SSL, comme les connexions HTTPS avec semi pont/pont complet, le stapling OCSP, les tickets de session, la confidentialité persistante parfaite, les impositions de protocole TLS et le chiffrement au niveau du champ.

Contrôle d'accès

Avec Amazon CloudFront, nous pouvons limiter l'accès à nos contenus via un certain nombre de capacités. Avec les URL signées et les cookies signés, nous pouvons prendre en charge l'authentification des jetons afin de limiter l'accès aux visionneuses authentifiées uniquement. La capacité de restriction géographique nous permet d'empêcher les utilisateurs situés dans des emplacements géographiques spécifiques d'accéder à des contenus que vous diffusez via CloudFront.

Conformité

L'infrastructure et les processus CloudFront sont tous conformes à PCI-DSS Niveau 1, HIPAA et ISO 9001, ISO 27001, SOC (1, 2 et 3) pour diffuser en toute sécurité nos données les plus sensibles.

Seulement, c’est bien beau de placer une distribution CloudFront devant son API, mais si celle-ci peut être contournée, cela perd tout de suite de son intérêt. Nous allons donc ici mettre en place une architecture permettant d’avoir accès à notre API uniquement via notre distribution CloudFront et bloquer les accès directs à l’API aux petits malins essayant de contourner la distribution.

Cognito

Dans un premier lieu parlons de l’identification via Cognito : Ce service AWS utilise le protocole OAuth2 qui est le protocole standard d’autorisation pour les API. Le fonctionnement mis en place ici est simple : on veut pouvoir authentifier à la fois des utilisateurs mais aussi des applications client. On va donc définir un UserPool classique et deux UserPoolClients qui vont lui être associés.

  • Le premier, UsernameClient va permettre à un utlisateur de s’enregistrer via son email et un mot de passe. Il pourra ensuite récupérer un token d’authentification.
  • Le second, ApiClient va permettre l'identification server-to-server, c'est-à-dire qu’avec l’ID du client et son secret on va pouvoir directement récupérer un token.

Nous avons choisi d’implémenter les deux pour montrer que c’était possible et donner une façon de le faire, bien évidemment certains projets ne nécessitent pas les deux systèmes d’authentification. Il faudra adapter cette partie en fonction du cas d’usage.

Une fois le token récupéré, on va pouvoir envoyer notre requête à CloudFront en étant sûr qu’on est bien identifiés grâce au header Authorization. Ce qui nous amène à la deuxième partie, comment forcer le passage par la distribution CloudFront devant notre API avec WAF et Secrets Manager.

Mise en place de Cognito avec SAM

La mise en place de la sécurité Cognito implique un certain nombre de ressources à mettre en place dans le template SAM:

Cognito User Pool: le User Pool n’est ni plus ni moins qu’un répertoire d’utilisateurs pour notre application, nous choisissons ici des paramètres très basiques, à noter que ceux-ci peuvent être bien plus personnalisés (voir Documentation)

Cognito User Pool Domain: Cette ressource nous permet de configurer l'adresse de nos pages web d'inscription et de connexion. Encore une fois, pour de plus amples informations nous vous invitons à lire la documentation

Cognito User Pool Clients: Une application est une entité au sein d'un groupe d'utilisateurs qui a la permission d'appeler des opérations API non authentifiées (opérations qui n'ont pas d'utilisateur authentifié). Il s'agit par exemple d'opérations d'enregistrement, de connexion et de gestion des mots de passe oubliés. Encore une fois, ici nous en avons défini 2, un pour l’identification côté client et l’autre pour l’identification côté serveur.

Plus d’informations pour la personnalisation de ces ressources ici.

Cognito Resource Server: C’est un serveur de ressources dont l'accès est protégé. Il traite les demandes authentifiées provenant d'une application qui possède un token. Cette ressource est nécessaire pour créer un authorizer dans l’API. Plus d’informations sur cette ressource ici.

L’inclusion du Cognito pour l’API inclut également des modifications sur la déclaration de l’API dans le template, il faut en effet rajouter un authorizer dans la déclaration de celle-ci:

WAF et Secret Manager

Secrets Manager

AWS Secrets Manager est le service AWS fournissant un coffre-fort sécurisé dans lequel est stocké les secrets nécessaires pour accéder à nos applications, services ou ressources informatiques. Le service nous permet de faire pivoter, de gérer et de récupérer facilement les informations d'identification des bases de données, les clés API et d'autres secrets tout au long de leur cycle de vie. Les utilisateurs et les applications récupèrent les secrets en appelant les API de Secrets Manager, ce qui élimine la nécessité de coder en dur les informations sensibles en “plain text”.

WAF

Nous placerons devant l’API, un Web Application Firewall (WAF) Régional permettant de mettre en place une Web Access Control List (Web ACL).

Une liste de contrôle d'accès web nous permet d’avoir un contrôle précis sur les demandes web auxquelles notre API Amazon API Gateway va répondre. Nous pouvons autoriser ou bloquer les types de requêtes suivants :

  • Provenant d'une adresse IP ou d'une plage d'adresses IP ;
  • Provenant d'un pays ou de pays spécifiques
  • Contenant une chaîne spécifiée ou correspondant à un modèle d'expression régulière (regex) d'un ensemble particulier de demandes
  • dépassant une longueur spécifiée ;
  • contenant un code SQL malveillant (appelé injection SQL) ;
  • contenant des scripts malveillants (appelés scripts inter-site).

Ici, nous nous servirons des règles mises en place par la Web ACL pour vérifier si la valeur du header X-Origin-Verify existe et est bien égale à la valeur stockée dans Secret Manager, au sein d’AWS, donc inaccessible au grand public.

Si cela est bien le cas, cela signifie que la requête auprès de l’API vient bien de la distribution CloudFront, et la requête ne sera donc pas interdite par le WAF.

Rotation du secret

Bien que le service Secrets Manager offre une rotation de secret intégrée pour certains services (Amazon RDS, Redshift, DocumentDB…), ce n’est pas le cas pour notre use case, nous devons donc définir nous même la procédure de rotation, ainsi que l’intervalle auquel nous souhaitons modifier ce secret.

Pour cela nous devons coder une fonction lambda (dite de rotation) qui a pour rôle de modifier la valeur du secret dans Secrets Manager et dans les services utilisés, ici la Web ACL et la distribution CloudFront.

Nous ne rentrerons pas dans les détails du code, il faut juste retenir que cette fonction va créer un nouveau secret “en attente”, le tester auprès des différents services, si les tests sont validés, va changer le statut du secret de “en attente” à “actuel” et supprimer l’ancien secret. Vous êtes libres d’aller voir le code de la fonction, et plus d’informations sont disponibles sur la documentation officielle d’AWS.

Notons également que nous avons choisi de conserver l’autorisation sur le précédent secret dans les règles de la Web ACL en raison du temps nécessaire à la mise à jour de la distribution CloudFront, qui peut parfois prendre plusieurs heures. Ceci afin de ne pas refuser des requêtes pouvant être légitimes.

Mise en place des ressources avec SAM

WAF Rule Group: C’est dans le WAF Rule Group que nous définissons une règle “OR” qui va autoriser 2 secrets. C’est grâce à ce statement que nous pouvons toujours accepter le précédent secret mais également l’actuel afin de ne pas rejeter des requêtes légitimes au vu du temps que prend CloudFront pour se mettre à jour.

Il faut également ajouter le custom header “X-Origin-Verify” au CloudFront, et qu’il référence la même valeur que celle attendue par le WAF :

Finalement, la ressource WebACLAssociation, définie dans le template permet, elle, de placer notre WebACL devant notre API afin de la protéger. Comme vous pouvez le voir, la déclaration de cette association est très simple :

Pour la rotation du secret, la Lambda doit tout de même être définie dans le template SAM, avec un certain nombre de variables d’environnement ainsi que les politiques d’accès aux ressources qui conviennent :

De plus il faudra donner la permission au Secrets Manager d’invoquer la fonction ainsi que créer un rotation schedule permettant de spécifier la fréquence à laquelle nous souhaitons que la valeur du secret soit modifiée:

Pour les autres ressources liées au WAF et au Secrets Manager, elles sont toutes présentes dans le fichier template.yaml à la racine du projet.

Déployez votre application full serverless

Vous cherchez à déployer une application serverless sécurisée et développée en TypeScript ? Après avoir lu ces trois articles et en utilisant le blueprint, cela devrait maintenant être un jeu d’enfant car l’architecture globale de l’application ne devrait pas être sujette à changement. En effet, seule la partie applicative, dépendant du besoin utilisateur, peut être adaptée et modifiée sans perturber le fonctionnement global de l’application. Il vous suffira de modifier le template SAM expliqué dans l’article 2 et d’ajouter vos fonctions et dépendances associées aux dossiers correspondants de l’arborescence (voir article 1).

La sécurisation de l’API peut elle aussi être personnalisée selon vos besoins, notamment via le choix de client Cognito ou bien par la modification de la période de rotation du secret utilisé par le WAF. Une fois toutes vos ressources mises en place, vous n’avez plus qu’à suivre le processus simple de build/deploy pour obtenir une application entièrement serverless déployée sur le cloud AWS.

Coûts

Il faut cependant noter les coûts qu’impliquent la mise en place de toutes ces ressources. Bien que le côté serverless de cette architecture permette de réduire les coûts, il nous semble important de détailler le mode de facturation du WAF et du Secret Manager. Sur notre plateforme de démonstration, le coût global mensuel était inférieur à $5.

WAF

Les frais d’implémentation d’un WAF sont détaillés par AWS dans leur documentation. Ce qu’il est important de retenir est le tableau suivant:

Type de ressources

Prix

ACL Web

5,00 USD par mois (au prorata horaire)

Règle

1,00 USD par mois (au prorata horaire)

Demande

0,60 USD par tranche de 1 million de demandes

Pour notre exemple qui inclut 1 ACL Web, 1 Rule Group (avec une seule règle), le WAF coûterait 6 USD par mois, auquel viennent s’ajouter les frais liés à la demande.

Nous pouvons cependant noter que si un WAF est déjà utilisé dans une architecture, l’implémentation de la règle de vérification du header n’implique qu’un coût de 1 USD par mois.

Secrets Manager


Pour ce qui est de la tarification d’un Secrets Manager, elle est décrite ici. Bien que les coûts impliqués soient minimes, il nous semblait important de les détailler. Secrets Manager facture 0,40 USD par secret stocké par mois, et 0,05 USD par tranche de 10 000 appels vers l’API Secrets Manager. L’architecture ici mise en place a pour seul appel vers l’API la rotation de secret. Si celle-ci est effectuée 1 fois par jour, le coût total du Secrets Manager pour cette solution devrait tourner autour de 0,40 USD par mois.