Article original : How to Securely Deploy APIs to Amazon Lambda – A Practical Guide

Les cyberattaques contre les API (Application Programming Interfaces) sont en augmentation. Ces attaques proviennent de problèmes liés à l'authentification, à l'autorisation, à l'exposition inutile de données, au manque de limites de requêtes, à la consommation de ressources et à l'utilisation d'API tierces vulnérables.

Des failles dans les API peuvent survenir avant que les requêtes n'atteignent les API, au sein du code hébergeant les API, et même le long du chemin de communication des API avec les services en aval, les dépendances ou d'autres microservices.

Les attaquants exploitent les failles des API pour accéder à des données confidentielles, collecter ou manipuler des données, ou même rendre votre service indisponible via des attaques par déni de service distribué (DDoS).

Dans cet article, vous apprendrez à déployer vos API dans Lambda et à appliquer certaines mesures de sécurité avant la fonction, au sein de la fonction et après la fonction.

Table des matières

Qu'est-ce qu'une API ?

L'accent de cet article est mis sur la sécurité des interfaces de programmation d'applications (API). Une API est une interface qui connecte deux programmes ou applications, leur permettant d'échanger des données et de communiquer.

Une API peut être interne à une organisation ou appartenir à un tiers qui permet à d'autres utilisateurs de consommer leurs données via l'API.

Prérequis

Bien que ce tutoriel soit accessible aux débutants, vous aurez besoin des prérequis suivants pour le suivre sans encombre :

  • Une connaissance de base du Cloud AWS.

  • Un compte AWS avec un accès administrateur.

  • AWS CLI. Vous pouvez trouver le guide d'installation ici. Suivez les instructions pour votre système d'exploitation.

  • Python. Vous pouvez visiter le site de documentation officielle de Python pour savoir comment télécharger et installer Python pour votre système d'exploitation spécifique.

  • Pipenv ou tout outil de création d'environnement virtuel Python. Vous pouvez trouver le guide d'installation de Pipenv ici.

  • Une connaissance de base de Git.

  • Un client API, comme Postman ou Thunderclient.

Objectif du projet

À la fin de ce projet, vous devriez être capable de déployer des API dans Lambda de manière sécurisée, en tirant parti des services de sécurité natifs du cloud AWS.

Architecture globale du projet

Voici l'architecture du flux de travail du projet :

Diagramme architectural du projet

Comme le montre le diagramme architectural, lorsqu'un utilisateur envoie une requête (un objet JSON composé du nom de l'utilisateur) à une API hébergée dans Lambda, l'utilisateur est d'abord authentifié par un service d'authentification appelé Amazon Cognito.

La requête passe par un Web Application Firewall, puis par une API Gateway. API Gateway effectuera une vérification pour voir si l'utilisateur est autorisé à accéder à l'API en utilisant le jeton (token) que l'utilisateur envoie avec la requête après l'authentification. API Gateway laisse ensuite passer le trafic vers l'API si l'utilisateur est autorisé.

La requête de l'utilisateur arrivera d'abord à une fonction Lambda externe, qui enregistrera ensuite le nom de l'utilisateur sous forme de message dans un sujet (topic) Simple Notification Service (SNS). Cela invoquera ensuite une Lambda interne pour s'exécuter et enregistrer la sortie dans les journaux Amazon CloudWatch. Le sujet SNS sera accédé par la Lambda externe à l'aide de l'identifiant unique du SNS stocké dans Amazon Secrets Manager.

Configuration AWS

Vous devrez configurer un environnement AWS pour commencer. Cela nécessite la création d'un compte si vous n'en avez pas déjà un.

Après la création du compte, un utilisateur racine (root user) est automatiquement créé, avec tous les privilèges attachés. La meilleure pratique de sécurité consiste à créer un autre utilisateur avec des privilèges d'administrateur et à utiliser cet utilisateur pour les tâches suivantes.

Ensuite, créez une clé d'accès pour cet utilisateur, qui se compose généralement de deux parties (ID de clé d'accès et Clé d'accès secrète) en naviguant vers :

IAM —→ Users —→ Create Access key

Suivez les instructions et choisissez l'option Command Line Interface. Cochez la case Confirmation et procédez à la création de la clé. Téléchargez le fichier CSV fourni, ou copiez manuellement l' Access Key ID et la Secret Access Key. Enregistrez-les en toute sécurité.

Tableau de bord IAM

Page utilisateur IAM

Page de création de clé d'accès

Page d'option d'utilisation de la clé d'accès

Page de définition de tag de clé d'accès

Téléchargement de la clé d'accès

Ouvrez votre terminal et exécutez les commandes suivantes à l'aide de l'AWS CLI :

aws configure

La commande ci-dessus affichera des invites pour fournir les composants de la Access Key créée précédemment et votre région par défaut (la région AWS hébergeant le service avec lequel vous avez l'intention d'interagir).

Cloner le projet

Dans l'étape suivante, vous clonerez le dépôt GitHub contenant les actifs et les ressources utilisés dans la mise en œuvre du projet.

Visitez l' URL du projet et clonez le dépôt localement.

git clone <repository_clone_url>

Configurer Simple Notification Service

Amazon Simple Notification Service (SNS) connecte les composants du système, permettant une communication et une messagerie asynchrones entre eux.

Recherchez SNS sur la console, cliquez dessus et créez un sujet (topic) auquel vos API enverront des messages. Après avoir créé avec succès un sujet, naviguez vers celui-ci, et dans les détails du sujet, vous trouverez l' ARN du sujet. Un ARN est un Amazon Resource Name, et c'est une chaîne unique attachée à une ressource que vous avez créée sur AWS pour aider à identifier la ressource. Copiez l' ARN du sujet.

Tableau de bord SNS

Créer un sujet SNS

Détails du sujet

Politique d'accès au sujet SNS

Sujet créé

Configurer Secrets Manager

Amazon Secrets Manager est utilisé pour stocker, gérer et récupérer des informations sensibles telles que des clés, des identifiants, des jetons, etc. Vous y stockerez l' Topic ARN créé précédemment. Avec cette approche, vous démontrerez comment votre API peut accéder en toute sécurité aux données et informations dont elle a besoin pour fonctionner.

Allez dans Secrets Manager sur la console AWS et créez un secret. Fournissez les détails du secret et ajoutez un nouveau secret nommé TOPIC_ARN comme clé et l'ARN réel du sujet SNS comme valeur.

Console Secrets Manager

Créer un secret

Choisir d'autres types de secret

Détails du secret

Stockage final du secret

Ensuite, vous créerez des fonctions Lambda pour servir vos API et consommer la sortie des API. Il y a trois fonctions Lambda à configurer. Deux des fonctions hébergeront des API, chacune ne pouvant être accédée que par des utilisateurs spécifiques. Celles-ci seront appelées ExternalLambda. La troisième Lambda consommera la sortie des fonctions Lambda externes via SNS.

Configurer la Lambda interne

AWS Lambda est un service serverless sur AWS que les utilisateurs peuvent exploiter pour exécuter des fonctions d'application ou du code en cas de besoin. Vous êtes facturé pour votre fonction Lambda en fonction du nombre d'invocations de la fonction, de la durée de chaque invocation et de la quantité de mémoire allouée à la fonction. Lambda peut être provisionné pour utiliser n'importe quel runtime, tel que Python ou NodeJS. Dans cette démonstration, nous nous concentrerons sur le runtime NodeJS.

Maintenant que vous savez ce qu'est Lambda et ce qu'il fait, vous pouvez en créer une. Appelons la première fonction Lambda InternalLambda. Sur la console AWS, recherchez Lambda, et sur le tableau de bord Lambda, cliquez sur Create a function et fournissez les détails. Nous utiliserons Node.js – JavaScript côté serveur comme runtime de choix.

AWS Lambda

Détails Lambda

Pour les détails des Permissions, laissez Lambda créer un IAM Role par défaut. Ce rôle par défaut est nommé en fonction de votre fonction, et les permissions attachées au rôle permettent à votre fonction Lambda d'envoyer des journaux à CloudWatch, un autre service AWS utilisé pour la surveillance et l'observabilité.

Permissions Lambda

39d50020-d0bf-4cfe-950f-eec8a2ff8989

Comme vous pouvez le voir dans la dernière image ci-dessus, la fonction Lambda que vous avez créée a besoin d'un déclencheur (trigger) et parfois d'une destination. Pour votre InternalLambda, le déclencheur est le sujet SNS que nous avons configuré précédemment. Cette Lambda lira les messages qui lui ont été publiés, puis vous pourrez accéder au message depuis votre client ou même les journaux CloudWatch.

Pour y parvenir, cliquez sur le bouton Add trigger et fournissez les détails.

Ajouter SNS à Lambda

ARN SNS

Aperçu InternalLambda

Ensuite, vous fournirez le code que vous souhaitez invoquer via Lambda. Trouvez le code dans le dépôt GitHub que vous avez cloné précédemment. Collez le code dans l'espace de code de la fonction Lambda et cliquez sur Deploy pour déployer la fonction.

secure-lambda/InternalLambda/index.js

export const handler = async (event) => {
    try {
        console.log('Request successfully received from SNS');                            

        let name = event['Records'][0]['Sns']['Message'];
        let response = {
            statusCode: 200,
            body: JSON.stringify(`Hello ${name}. Greetings from InternalLambda!`),
        };       
        console.log('Response: ', response);                                                
        return response;
    } catch (err) {
        let response = {
            statusCode: 500,
            body: JSON.stringify('An error occurred while processing your request.'),
        };

        console.error('Error processing event', err);
        return response;
    }   
};

La fonction définie dans le fichier index.js ci-dessus prend simplement l'objet event qui lui est envoyé par SNS et en extrait l'attribut Message. Nous utilisons console.log ici pour visualiser les sorties de la fonction et nous assurer qu'elle se comporte comme prévu. Ne l'utilisez simplement pas dans une application prête pour la production.

Code InternalLambda

Configurer la Lambda externe

Vous allez créer deux fonctions Lambda externes : 1 et 2. Ces deux fonctions recevront les requêtes des utilisateurs, les traiteront et publieront des messages sur votre sujet SNS.

Sur la console Lambda, créez une autre fonction et nommez-la ExternalLambda1. Laissez Lambda créer un rôle IAM par défaut, comme précédemment.

Créer ExternalLambda1

Aperçu ExternalLambda1

Collez l'extrait de code ci-dessous dans l'espace de code ExternalLambda1 :

secure-lambda/ExternalLambda1/insex.js

import {
  GetSecretValueCommand,
  SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";

import { SNSClient, 
    PublishCommand 
} from "@aws-sdk/client-sns";

const secretsManagerClient = new SecretsManagerClient();

const snsClient = new SNSClient({});

// Fetch topicArn from AWS Secrets Manager
async function getSecretValue(secretName) {
    try {
        const data = await secretsManagerClient.send(
                            new GetSecretValueCommand({
                            SecretId: secretName,
                            }),
                        );
        if (data.SecretString) {
            return JSON.parse(data.SecretString);
        }   else {
            let buff = Buffer.from(data.SecretBinary, 'base64');
            return JSON.parse(buff.toString("utf-8"));
        }
    } catch (err) {
        console.error('Error retrieving secret', err);                             // added for debugging
        throw err;
    }
}                                        

export const handler = async (event) => {

    let name = event['name'];
    console.log(`Request successfully received from ${name}`);    

    // Retrieve SNS Topic ARN from Secrets Manager
    let topicArn;
    let response;
    try {
        const secret = await getSecretValue('LambdaSNSTopicARN');
        topicArn = secret.TOPIC_ARN;
    } catch (err) {
        response = {
            statusCode: 500,
            body: JSON.stringify('An error occured, try again later.'),
        };
        console.error('Failed to load SNS Topic ARN from Secrets Manager', err);
        return response;        
    }

    // Publish to SNS topic
   try {
        const snsResponse = await snsClient.send(
        new PublishCommand({
            Message: name,
            TopicArn: topicArn,
        })
        );
        console.log("Message published successfully:", snsResponse.MessageId);
        response = {
            statusCode: 200,
            body: JSON.stringify(`Hello ${name}. Greetings from ExternalLambda1! Message forwarded to InternalLambda.`),
        };
        return response;
  } catch (err) {
        response = {
            statusCode: 500,
            body: JSON.stringify(`Sorry ${name}.An error occurred while processing your request.`),
        };
        console.error("Failed to publish message:", err);
        return response;
  }  
};

Le code ci-dessus utilise le SDK AWS pour récupérer l'ARN du sujet SNS créé précédemment à partir de Secrets Manager. Il publie ensuite un message sur le sujet.

Le SDK est déjà installé dans la fonction Lambda. En dehors de Lambda, le SDK doit être explicitement installé. La fonction reçoit son event du client via API Gateway, que nous configurerons plus tard.

Le sujet SNS que vous avez créé précédemment sera la destination de cette fonction. Pour que Lambda puisse publier un sujet sur SNS, il a besoin de la permission nécessaire attachée à son rôle IAM. AWS peut s'en charger automatiquement lors de votre configuration, comme indiqué ci-dessous.

Pour le déclencheur, vous utiliserez un autre service connu sous le nom d' API Gateway. Plus d'informations à ce sujet plus tard.

ExternalLambda1 Ajouter une destination

ExternalLambda1 Permissions de destination

Suivez les mêmes étapes pour provisionner une autre Lambda connue sous le nom d' ExternalLambda2.

ExternalLambda2

Le résultat de la configuration de la Lambda externe est présenté ci-dessous :

Aperçu ExternalLambda2

Collez le code ci-dessous dans ExternalLambda2. Il remplit la même fonction qu' ExternalLambda1, mais leur sortie diffère. Chacune des deux fonctions Lambda recevra du trafic pour un utilisateur spécifique autorisé à accéder à la fonction.

secure-lambda/ExternalLambda2/index.js

import {
  GetSecretValueCommand,
  SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";

import { SNSClient, 
    PublishCommand 
} from "@aws-sdk/client-sns";

const secretsManagerClient = new SecretsManagerClient();

const snsClient = new SNSClient({});

// Fetch topicArn from AWS Secrets Manager
async function getSecretValue(secretName) {
    try {
        const data = await secretsManagerClient.send(
                            new GetSecretValueCommand({
                            SecretId: secretName,
                            }),
                        );
        if (data.SecretString) {
            return JSON.parse(data.SecretString);
        }   else {
            let buff = Buffer.from(data.SecretBinary, 'base64');
            return JSON.parse(buff.toString("utf-8"));
        }
    } catch (err) {
        console.error('Error retrieving secret', err);  
        throw err;
    }
}                                        

export const handler = async (event) => {

    let name = event['name'];
    console.log(`Request successfully received from ${name}`);    

    // Retrieve SNS Topic ARN from Secrets Manager
    let topicArn;
    let response;
    try {
        const secret = await getSecretValue('LambdaSNSTopicARN');
        topicArn = secret.TOPIC_ARN;
    } catch (err) {
        response = {
            statusCode: 500,
            body: JSON.stringify('An error occured, try again later.'),
        };
        console.error('Failed to load SNS Topic ARN from Secrets Manager', err);
        return response;        
    }

    // Publish to SNS topic
   try {
        const snsResponse = await snsClient.send(
        new PublishCommand({
            Message: name,
            TopicArn: topicArn,
        })
        );
        console.log("Message published successfully:", snsResponse.MessageId);
        response = {
            statusCode: 200,
            body: JSON.stringify(`Hello ${name}. Greetings from ExternalLambda2! Message forwarded to InternalLambda.`),
        };
        return response;
  } catch (err) {
        response = {
            statusCode: 500,
            body: JSON.stringify(`Sorry ${name}.An error occurred while processing your request.`),
        };
        console.error("Failed to publish message:", err);
        return response;
  }              
};

Avant de continuer, vous devez modifier les rôles IAM de la Lambda externe. Actuellement, les rôles IAM n'ont que les permissions d'écrire dans CloudWatch et SNS (ajoutées automatiquement). La Lambda externe a également besoin de la permission de récupérer l'ARN du sujet SNS créé précédemment.

L'idée ici est de montrer comment exploiter un gestionnaire de secrets, tel qu'AWS Secrets Manager, pour stocker des informations ou des données sensibles, tout en y accédant de manière sécurisée. Cette approche est plus sûre que de stocker l'ARN en tant que variable d'environnement au sein de Lambda.

Naviguez vers IAM et cliquez sur l'onglet Policies à gauche. Cela vous amène à une liste de politiques. Ensuite, cliquez sur Create policy.

Politiques IAM

Recherchez secrets manager dans l'éditeur de politique.

Éditeur de politique

Éditeur de politique 2

Sélectionnez les permissions dont Lambda a besoin pour accéder à Secrets Manager. Dans ce cas, ce serait Read —> GetSecretValue.

Éditeur de politique - Spécifier les permissions

Sélectionnez Specific pour les ressources, et cliquez sur Add ARNs. Sur l'onglet suivant, ajoutez les détails du secret Secrets Manager créé précédemment.

Éditeur de politique - Sélectionner l'accès

L'ARN du secret sera renseigné ici.

Éditeur de politique - Ajouter l'ARN Secrets Manager

Ensuite, donnez un nom à la politique et créez-la.

Éditeur de politique - Créer la politique

Politique nouvellement ajoutée

Ensuite, naviguez vers Roles et recherchez les rôles IAM attribués aux fonctions Lambda externes. Ceux-ci sont nommés en fonction de la Lambda.

Rôles IAM

Rôles IAM Lambda

Cliquez sur Add permissions pour ajouter une nouvelle permission au rôle IAM sélectionné.

Rôle ExternalLambda1

Rôle ExternalLambda1 - Politique ajoutée

Rôle ExternalLambda2

Rôle ExternalLambda2 - Politique ajoutée

Configurer Web Application Firewall

Un pare-feu est un système placé devant une application, une charge de travail, des API, etc., pour inspecter le trafic, le filtrer et soit autoriser, soit bloquer le trafic en fonction de certaines règles préconfigurées.

Pour ce projet, vous utiliserez le service AWS Web Application Firewall (WAF) pour inspecter les requêtes des utilisateurs avant d'acheminer le trafic vers vos API s'exécutant dans Lambda.

Rendez-vous sur la console AWS et recherchez WAF.

AWS Web Application Firewall

Cliquez sur l'onglet IP sets à gauche. Cela vous permettra de créer une liste d'adresses IP que vous souhaitez autoriser (comme dans ce cas) ou refuser.

Page IP Sets

Configuration IP Set

Les adresses IP doivent inclure un bloc CIDR. Par exemple, si vous ajoutez une seule adresse IP, elle doit être X.X.X.X/32. Il en va de même pour les plages d'adresses IP telles que X.X.X.X/24.

Aperçu IP Set

Ensuite, cliquez sur l'onglet Web ACLs, puis sur Create web ACL.

Page Web ACL

Choisissez Regional resources comme type de ressource, et entrez votre région. Il est préférable de conserver toutes les ressources que vous créez dans ce projet dans la même région. Donnez un nom à votre Web ACL, puis cliquez sur suivant.

Description Web ACL

Ajoutez des règles à la Web ACL.

Règle WAF

Ajouter une règle

Choisissez un type de règle. Dans ce cas, vous utiliserez IP set, et donnerez un nom à la règle. Choisissez l'IP set créé précédemment.

Sélectionnez Source IP address, et Count comme action. Pour ce projet, vous vous concentrerez sur le comptage des requêtes envoyées à vos API. Mais comme le montre l'image ci-dessous, vous pouvez effectuer d'autres actions, telles qu'autoriser, bloquer, etc.

Configuration de règle WAF

Votre configuration de règle finale apparaîtra de cette façon.

Aperçu de règle WAF

Faites défiler vers le bas, puis cliquez sur Create web ACL.

Créer une règle

Tableau de bord Web ACL

Configurer les pools d'utilisateurs Cognito

Amazon Cognito est un service de gestion d'identité utilisé pour créer et gérer des utilisateurs. Vous pouvez l'exploiter pour authentifier et autoriser les utilisateurs à accéder à des applications, des API ou d'autres charges de travail.

Vous créerez des User Pools (pools d'utilisateurs) au sein de Cognito et ajouterez un utilisateur à chaque pool. Vous configurerez la manière dont ces utilisateurs peuvent être authentifiés et autorisés à accéder aux fonctions Lambda externes déjà créées.

Recherchez Cognito sur AWS.

Amazon Cognito

Cliquez sur Get started for free, puis sur Create user pool.

Créer un pool d'utilisateurs

Sélectionnez Single-page application (SPA), donnez au pool d'utilisateurs le nom MyUserPool1, et sélectionnez Email comme option de connexion. Cela signifie que l'attribut principal que les utilisateurs fourniront lors de l'inscription et de la connexion sera leur adresse e-mail. Laissez tout le reste par défaut. Nous garderons les choses aussi simples que possible.

Configuration du pool d'utilisateurs

Configuration du pool d'utilisateurs 2

Configuration du pool d'utilisateurs 3

Après avoir créé le pool d'utilisateurs, vous trouverez la page ci-dessous. Vous pouvez afficher la page de connexion et d'inscription pour le pool que vous venez de créer en cliquant sur le bouton View login page.

URL de connexion du client d'application Cognito

Vous pouvez ajouter des App clients à votre pool d'utilisateurs. Par défaut, un client nommé MyUserPool1 sera ajouté au pool. Naviguez vers votre pool d'utilisateurs et cliquez sur App clients pour voir les détails de ce client. Notez le Client ID. Vous apporterez également quelques modifications au client d'application en cliquant sur le bouton Edit.

Aperçu du client d'application du pool d'utilisateurs

Vous modifierez le champ Authentication flows en cochant les cases Sign in with username and password… et Sign in with server-side administrative credentials…. Ces modifications vous permettront d'authentifier l'utilisateur qui sera ajouté à ce client par programmation, plutôt que via une interface utilisateur. Avec cette approche, nous pouvons récupérer le jeton attribué à l'utilisateur par Cognito et utiliser ce jeton pour autoriser l'accès à Lambda.

Modifier le client d'application

Maintenant, ajoutez un utilisateur à ce pool. L'utilisateur a besoin d'une adresse e-mail valide. Vous aurez besoin de l'URL de la page de connexion pour créer l'utilisateur.

Cognito Créer un nouvel utilisateur

Vous devez avoir accès à l'e-mail utilisé pour créer l'utilisateur. Récupérez le code envoyé à l'adresse e-mail et soumettez-le pour confirmer le compte.

Cognito Confirmer l'e-mail

Inscription réussie Cognito

Utilisateurs du pool d'utilisateurs

Suivez les mêmes étapes et créez un autre pool d'utilisateurs nommé MyUserPool2. Ajoutez un utilisateur avec un e-mail différent à ce pool.

Configurer API Gateway

API Gateway est un service utilisé pour gérer l'accès et acheminer le trafic vers les services backend d'API tels que les API. Il sert de proxy inverse et fournit une couche de sécurité supplémentaire pour les services backend.

Vous configurerez API Gateway pour diriger le trafic vers vos fonctions Lambda.

Naviguez vers API Gateway et cliquez sur Create an API.

API Gateway

Sélectionnez l'option REST API —→ Build.

Sélectionner le type d'API

Sélectionnez New API, fournissez un nom et choisissez Regional comme type de point de terminaison (endpoint) d'API. Le type d'adresse IP peut être IPv4 ou Dualstack. Nous sélectionnerons IPv4 ici. Puis créez.

Configuration API Gateway

Une partie importante de la configuration d'API Gateway pour ce projet est l'Authorizer. API Gateway utilise l'Authorizer pour autoriser le trafic des clients vers les services backend.

Vous créerez deux Authorizers. Chacun sera connecté à l'un des pools d'utilisateurs que vous avez configurés précédemment. Sur le côté gauche de l'API Gateway que vous avez configurée, cliquez sur Authorizers —→ Create authorizer.

Authorizer API Gateway

Fournissez le nom AGAuthorizer1, et sélectionnez Cognito comme type d'Authorizer. Ajoutez le pool d'utilisateurs pour MyUserPool1 créé précédemment. Pour la source du jeton (Token source), utilisez Authorization. Lorsque vous envoyez une requête depuis votre client API, un jeton sera ajouté à l'en-tête de la requête pour l'autorisation. La clé du jeton sera nommée Authorization, tandis que la valeur sera le jeton lui-même.

Configuration Authorizer1

Créez une autre autorisation pour MyUserPool2 de la même manière.

Configuration Authorizer2

Les deux Authorizers apparaîtront de cette façon.

Aperçu des Authorizers

Ensuite, vous créerez des ressources et des points de terminaison au sein de l'API Gateway que vous avez définie.

Une resource dans API Gateway est utilisée pour regrouper certains points de terminaison dans un chemin spécifique. Vous définirez deux ressources au sein de l'API Gateway que vous avez créée. Cela créera deux chemins différents, / et .

Sur le tableau de bord API Gateway, naviguez vers votre Gateway, cliquez sur Create resource, définissez votre chemin racine ('/' dans votre cas), et fournissez le nom de la ressource (lambda1).

Ressource lambda1 API Gateway

Créez une autre ressource nommée lambda2.

Aperçu des ressources API Gateway

Maintenant, cliquez sur /lambda1, puis sur Create method pour définir un point de terminaison au sein de cette ressource. Vous utiliserez la méthode POST pour envoyer des requêtes au service backend via ce point de terminaison.

Configuration de méthode API Gateway

Pour le service backend ou le type d'intégration, sélectionnez Lambda function, et fournissez l'ARN d'ExternalLambda1.

Configuration de méthode API Gateway 2

Pour l'autorisation, sélectionnez AWS IAM —→ Cognito user pool authorizers —→ AGAuthorizer1. Laissez les autres configurations, puis créez le point de terminaison.

Configuration de méthode API Gateway 3

Répétez la même étape pour créer une méthode POST pour la ressource /lambda2. La method doit être attachée à ExternalLambda2 et AGAuthorizer2.

Déploiement API Gateway

L'API Gateway que vous avez créée doit être déployée pour devenir accessible. Le déploiement se fait généralement vers une étape (Stage).

Cliquez sur Deploy API, sélectionnez New stage et nommez l'étape development. Puis, déployez.

Étape API Gateway

Après le déploiement vers une étape, une URL d'invocation sera fournie. Elle servira d'URL de base pour les points de terminaison que vous avez définis.

Aperçu de l'étape API Gateway

L'étape que vous avez créée nécessite quelques modifications pour une sécurité accrue. Premièrement, vous devez attacher le WAF que vous avez créé précédemment. Deuxièmement, la limite de débit (rate limit) par défaut pour l'API déployée sur cette étape est de 10000. La limite de débit restreint la consommation excessive de ressources et protège votre API contre les abus. Pour ce projet, vous pouvez réduire la limite à 50.

Modifier l'étape API Gateway

Étape API Gateway - Ajouter une limite de débit et WAF

Pour tester la configuration d'API Gateway, cliquez sur le point de terminaison que vous souhaitez tester, puis sur le bouton Test. Ce test initial ne nécessite aucune autorisation, car le test est effectué directement au sein de la Gateway.

Test de point de terminaison API Gateway

Ajoutez des données JSON comme corps de la requête (Request body). La clé sera name, et la valeur sera n'importe quelle chaîne de caractères.

Test API Gateway 2

La réponse renvoyée par ExternalLambda1 affiche un code d'état 200 et un corps de réponse contenant exactement le message attendu de la fonction Lambda.

Réponse de test API Gateway

Si vous vous rendez dans les groupes de journaux CloudWatch (Log groups), vous devriez également trouver les groupes de journaux qui ont été automatiquement créés pour les fonctions Lambda. Cliquez sur le groupe de journaux pour ExternalLambda1 et naviguez vers le flux de journaux (Log stream) le plus récent. Vous devriez trouver les journaux de la requête que vous venez de faire depuis API Gateway.

Journaux CloudWatch pour le test

Journaux CloudWatch pour le test 2

Journaux CloudWatch - Sortie d'InternalLambda

Tester la configuration de bout en bout

Pour tester correctement notre configuration, et depuis Internet, envoyez la même requête depuis votre client API sans aucune information supplémentaire dans l'en-tête de la requête. Cela devrait renvoyer une erreur 401 – Unauthorized. C'est le comportement attendu.

Requête sans jeton

API Gateway attend un jeton d'autorisation pour chaque requête qu'elle reçoit avant d'acheminer le trafic vers le service backend approprié. Elle valide ce jeton via Cognito.

Vous allez simuler une connexion utilisateur pour chaque utilisateur ajouté aux pools d'utilisateurs Cognito afin d'obtenir un jeton pour l'utilisateur. Pour y parvenir, vous utiliserez les deux scripts Python que j'ai fournis ci-dessous :

secure-lambda/auth-scripts/user1.py

import boto3

client = boto3.client("cognito-idp")

response = client.initiate_auth(
    AuthFlow="USER_PASSWORD_AUTH",  # or ADMIN_USER_PASSWORD_AUTH if using admin creds
    AuthParameters={
        "USERNAME": "",             # user1 email
        "PASSWORD": ""              # user1 password
    },
    ClientId=""                     # Cognito App Client ID
)

id_token = response["AuthenticationResult"]["IdToken"]
access_token = response["AuthenticationResult"]["AccessToken"]
refresh_token = response["AuthenticationResult"]["RefreshToken"]

print("ID Token:", id_token)

En utilisant la bibliothèque Python boto3, vous initierez une requête d'authentification auprès de Cognito. Fournissez l'adresse e-mail et le mot de passe de l'utilisateur dans MyUserPool1. Ajoutez également l'ID client du client d'application.

Pour exécuter le script, créez un environnement isolé à l'aide de Pipenv, uv ou d'une bibliothèque similaire. Installez la dépendance utilisée dans le projet comme défini dans le Pipfile, et exécutez le script avec le shell Pipenv.

pipenv install
pipenv shell
Python secure-lambda/auth-scripts/user1.py

La commande Python renverra un jeton attribué à l'utilisateur. Ensuite, vous utilisez ce jeton pour autoriser un utilisateur à accéder à ExternalLambda1.

Ajouter un jeton à l'en-tête de la requête

Assurez-vous que l'URL de la requête POST est au format : . Vous devriez recevoir une réponse d'API Gateway indiquant le succès.

Maintenant, essayez d'accéder à ExternalLambda2 en utilisant le jeton de User1. Vous devriez recevoir un message Unauthorized. Notez que user1 recevra toujours un message non autorisé lorsqu'il tente d'accéder à ExternalLambda1 sans jeton d'autorisation dans l'en-tête, avec un mauvais jeton, ou lorsqu'il tente d'accéder à ExternalLambda2, pour lequel il n'est pas autorisé.

User1 accède à ExternalLambda2

Répétez le processus avec User2 en utilisant le jeton généré pour l'utilisateur dans MyUserPool2. Tout d'abord, testez l'accès à ExternalLambda2 sans jeton dans l'en-tête de la requête.

Requête User2 sans jeton

Puis testez l'accès avec le jeton.

Requête User2 avec jeton

Ensuite, essayez d'accéder à ExternalLambda1 en utilisant User2.

User2 accède à ExternalLambda1

Vous pouvez également visualiser le résultat de certaines des requêtes effectuées par votre client sur les journaux CloudWatch.

Sortie des journaux CloudWatch

Sortie des journaux CloudWatch 2

De plus, puisque WAF a été configuré précédemment pour compter les requêtes (bien que, dans un scénario réel, vous souhaitiez accomplir bien plus avec WAF, comme autoriser ou bloquer certains trafics), vous pouvez visualiser les activités capturées par WAF en naviguant vers le service sur AWS, puis en recherchant le WAF que vous avez configuré, et en naviguant vers Traffic overview.

WAF - Détails du trafic

WAF - Détails du trafic 2

Vous pouvez trouver d'autres détails, tels que les types d'appareils clients et l'origine des requêtes.

WAF - Détails du trafic 3

WAF - Détails du trafic 4

Nettoyage

Il est important de nettoyer les ressources créées jusqu'à présent après l'exercice pratique. En raison des dépendances entre les ressources, essayer de supprimer une ressource dont dépend une autre ressource peut entraîner une erreur. Vous devez donc les supprimer dans cet ordre :

  • Secrets Manager

  • Cognito – Utilisateurs, Client d'application, puis Pool d'utilisateurs

  • API Gateway – Points de terminaison/ Méthodes, Ressources, API, Étape

  • Web Application Firewall – IP Set, Web ACL

  • Toutes les fonctions Lambda

  • Rôles IAM Lambda et les politiques qui leur sont attachées

  • Groupe de journaux CloudWatch pour toutes les fonctions Lambda

  • Sujet SNS

De plus, vous pouvez désactiver ou supprimer les identifiants créés pour votre utilisateur IAM Admin s'ils ne sont pas utilisés.

Améliorations

Considérez les domaines suivants pour améliorer, appliquer les meilleures pratiques et renforcer davantage la posture de sécurité de vos systèmes.

  1. Utilisation de clés d'API

  2. Consommation d'API tierces

  3. Gestion de l'inventaire des API / documentation

  4. Provisionnement des ressources à l'aide de l'Infrastructure as Code

Conclusion

La sécurité à chaque couche d'un système informatique n'est pas négociable. Dans ce projet, nous avons démontré comment exploiter des solutions natives du cloud pour sécuriser des API hébergées dans un service serverless, en n'autorisant l'accès aux API qu'aux utilisateurs autorisés.

Je suis Agnes Olorundare, et vous pouvez en savoir plus sur moi sur LinkedIn.