Ceci est le deuxième article de notre série consacrée au déploiement de NGINX Open Source et de NGINX Plus en tant que passerelle API.
- Partie 1 fournit des instructions de configuration détaillées pour plusieurs cas d’utilisation.
- Initialement publié en 2018, cet article a été mis à jour afin de refléter les bonnes pratiques actuelles en matière de configuration des API, en utilisant des blocs
locationimbriqués pour acheminer les requêtes plutôt que des règles de réécriture.
Cet article approfondit ces cas d’usage et présente différentes mesures de protection pouvant être mises en œuvre pour protéger et sécuriser les services d’API backend en production :
- Limitation du débit
- Imposition de méthodes de requête spécifiques
- Application d’un contrôle d’accès granulaire
- Contrôle de la taille des requêtes
- Validation du corps des requêtes
- Partie 3 explique comment déployer NGINX Open Source et NGINX Plus en tant que passerelle API pour les services gRPC.
Remarque: Sauf indication contraire, toutes les informations de cet article s’appliquent à la fois à NGINX Open Source et à NGINX Plus. Pour faciliter la lecture, « NGINX » sera simplement utilisé dans la suite de cet article.
Limitation du débit
Contrairement aux clients basés sur un navigateur, les clients d’API individuels peuvent imposer une charge considérable à vos API, au point de consommer une part si importante des ressources système que les autres clients d’API se retrouvent de fait privés d’accès. Cette menace ne vient pas uniquement de clients malveillants : un client d’API défaillant ou présentant un bug peut entrer dans une boucle qui surcharge le backend. Pour éviter cela, nous appliquons une limitation du débit afin de garantir une utilisation équitable par chaque client et de préserver les ressources des services backend.
NGINX peut appliquer des limites de débit en fonction de n’importe quel attribut de la requête. L’adresse IP du client est généralement utilisée, mais lorsque l’authentification est activée pour l’API, l’identifiant du client authentifié constitue un attribut plus fiable et plus précis.
Les limites de débit sont définies dans le fichier de configuration principal de la passerelle API et peuvent ensuite être appliquées globalement, pour chaque API, voire pour chaque URI.
Chargement de l’exemple de code…
Dans cet exemple, la directive limit_req_zone à la ligne 4 définit une limite de 10 requêtes par seconde pour chaque adresse IP cliente ($binary_remote_addr), tandis que celle de la ligne 5 définit une limite de 200 requêtes par seconde pour chaque identifiant de client authentifié ($http_apikey). Cela montre comment définir plusieurs limites de débit indépendamment de l’endroit où elles sont appliquées. Une API peut appliquer plusieurs limites de débit simultanément ou des limites différentes selon les ressources.
Ensuite, dans l’extrait de configuration suivant, nous utilisons la directive limit_req pour appliquer la première limite de débit dans la section de stratégie de l’« API d’entrepôt » décrite dans la Partie 1<.htmla>. Par défaut, NGINX envoie la réponse 503 (Service indisponible) lorsque la limite de débit a été dépassée. Toutefois, il est utile que les clients de l’API sachent explicitement qu’ils ont dépassé leur limite de débit afin de pouvoir adapter leur comportement. À cette fin, nous utilisons la directive limit_req_status pour envoyer à la place la réponse 429 (Trop de requêtes).
Chargement de l’exemple de code…
Vous pouvez utiliser des paramètres supplémentaires avec la directive « limit_req » pour affiner la manière dont NGINX applique les limites de débit. Par exemple, il est possible de mettre les requêtes en file d’attente au lieu de les rejeter immédiatement lorsque la limite est dépassée, afin de laisser au débit des requêtes le temps de repasser sous la limite définie. Pour en savoir plus sur le réglage précis des limites de débit, consultez l’article « Limitation du débit avec NGINX et NGINX Plus » sur notre blog.
Application de méthodes de requête spécifiques
Avec les API RESTful, la méthode HTTP (ou verbe) constitue une partie importante de chaque appel d’API et joue un rôle essentiel dans la définition de l’API. Prenons comme exemple le service de tarification de notre API Warehouse :
GET/api/warehouse/pricing/item001renvoie le prix de l’article item001PATCH/api/warehouse/pricing/item001modifie le prix de l’article item001
Nous pouvons mettre à jour les définitions de routage des URI dans l’API Warehouse afin de n’accepter que ces deux méthodes HTTP pour les requêtes adressées au service de tarification (et uniquement la méthode « GET » pour les requêtes adressées au service de gestion des stocks).
Chargement de l’exemple de code…
Avec cette configuration, les requêtes adressées au service de tarification à l’aide de méthodes autres que celles répertoriées à la ligne 22 (ainsi que celles adressées au service d’inventaire à l’aide d’une méthode autre que celle indiquée à la ligne 13) sont rejetées et ne sont pas transmises aux services backend. NGINX envoie la réponse 405 (Méthode non autorisée) afin d’indiquer au client de l’API la nature précise de l’erreur, comme l’illustre la trace de console suivante. Lorsqu’une politique de sécurité imposant une divulgation minimale est requise, la directive error_page peut être utilisée pour convertir cette réponse en une erreur moins explicite, par exemple 400 (Requête incorrecte).
$ curl https://api.example.com/api/warehouse/pricing/item001{"sku":"item001","price":179.99}
$ curl -X DELETE https://api.example.com/api/warehouse/pricing/item001
{"status":405,"message":"Method not allowed"}Application d’un contrôle d’accès granulaire
La première partie de cette série expliquait comment protéger les API contre les accès non autorisés en activant des options d’authentification telles que les clés d’API et les jetons Web JSON (JWT). Nous pouvons utiliser l’identifiant authentifié, ou ses attributs, pour mettre en œuvre un contrôle d’accès précis.
Nous présentons ici deux exemples de ce type.
- Le premier exemple contrôle l’accès à une ressource d’API spécifique en étendant une configuration présentée dans la première partie et en utilisant l’authentification par clé d’API pour vérifier qu’un client d’API donné figure sur la liste d’autorisation.
- Le deuxième exemple détermine les méthodes HTTP qu’un client est autorisé à utiliser. Il met en œuvre la méthode d’authentification JWT mentionnée dans la première partie, en utilisant une revendication personnalisée pour identifier les clients API autorisés. (Notez que la prise en charge de JWT est réservée à NGINX Plus.)
Bien entendu, d’autres méthodes d’authentification peuvent être utilisées dans ces exemples de cas d’utilisation, telles que l’authentification HTTP Basic et l’introspection de jetons OAuth 2.0.
Contrôle de l’accès à des ressources spécifiques
Supposons que nous souhaitions autoriser uniquement les « clients d’infrastructure » à accéder à la ressource audit du service d’inventaire de l’API Warehouse. Lorsque l’authentification par clé d’API est activée, nous utilisons un bloc map pour créer une liste d’autorisation de noms de clients d’infrastructure, afin que la variable $is_infrastructure prenne la valeur 1 lorsqu’une clé d’API correspondante est utilisée.
Chargement de l’exemple de code…
Dans la définition de l’API Warehouse, nous ajoutons un bloc « location » pour la ressource d’audit de l’inventaire (lignes 15 à 20). Le bloc « if » garantit que seuls les clients d’infrastructure peuvent accéder à la ressource.
Chargement de l’exemple de code…
Notez que la directive « location » à la ligne 15 utilise le modificateur « = » (signe égal) pour établir une correspondance exacte avec la ressource audit. Les correspondances exactes ont priorité sur les définitions par défaut basées sur les préfixes de chemin utilisées pour les autres ressources. La trace suivante montre qu’avec cette configuration, un client qui ne figure pas sur la liste d’autorisation ne peut pas accéder à la ressource audit de l’inventaire. La clé d’API affichée appartient à client_two (tel que défini dans la Partie 1).
$ curl -H "apikey: QzVV6y1EmQFbbxOfRCwyJs35" https://api.example.com/api/warehouse/inventory/audit{"status":403,"message":"Forbidden"}Contrôler l’accès à des méthodes spécifiques
Comme indiqué ci-dessus, le service de tarification accepte les méthodes GET et PATCH, qui permettent respectivement aux clients de consulter et de modifier le prix d’un article donné. (Nous pourrions également choisir d’autoriser les méthodes POST et DELETE afin d’assurer une gestion complète du cycle de vie des données tarifaires.) Dans cette section, nous étendons ce cas d’utilisation afin de contrôler les méthodes que chaque utilisateur peut employer. Lorsque l’authentification JWT est activée pour l’API Warehouse, les autorisations de chaque client sont encodées sous forme de revendications personnalisées. Les JWT délivrés aux administrateurs autorisés à modifier les données tarifaires incluent la revendication "admin":true. Nous étendons maintenant notre logique de contrôle d’accès afin que seuls les administrateurs puissent effectuer des modifications.
Chargement de l’exemple de code…
Ce bloc map, ajouté à la fin du fichier api_gateway.conf, prend comme entrée la méthode de la requête ($request_method) et génère une nouvelle variable, $admin_permitted_method. Les méthodes en lecture seule sont toujours autorisées (lignes 62 à 64), mais l’accès aux opérations d’écriture dépend de la valeur de la revendication admin dans le JWT (ligne 65). Nous étendons maintenant la configuration de notre API Warehouse afin que seuls les administrateurs puissent modifier les prix.
Chargement de l’exemple de code…
L’API Warehouse exige que tous les clients présentent un JWT valide (ligne 7). Nous vérifions également que les opérations d’écriture sont autorisées en évaluant la variable « $admin_permitted_method » (ligne 25). Rappelons que l’authentification JWT est disponible exclusivement avec NGINX Plus.
Contrôle de la taille des requêtes
Les API HTTP utilisent couramment le corps de la requête pour transmettre des instructions et des données que le service d’API backend doit traiter. Cela vaut aussi bien pour les API XML/SOAP que pour les API JSON/REST. Par conséquent, le corps de la requête peut constituer un vecteur d’attaque contre les services d’API backend, qui peuvent être vulnérables aux attaques par dépassement de mémoire tampon lors du traitement de corps de requête très volumineux.
Par défaut, NGINX rejette les requêtes dont le corps dépasse 1 Mo. Cette limite peut être augmentée pour les API qui traitent spécifiquement des charges utiles volumineuses, comme dans le cas du traitement d’images, mais pour la plupart des API, nous définissons une valeur inférieure.
Chargement de l’exemple de code…
La directive « client_max_body_size » à la ligne 7 limite la taille du corps de la requête. Avec cette configuration, nous pouvons comparer le comportement de la passerelle API lorsqu’elle reçoit deux requêtes PATCH différentes adressées au service de tarification. La première commande curl envoie un petit fragment de données JSON, tandis que la seconde tente d’envoyer le contenu d’un fichier volumineux (/etc/services).
$ curl -iX PATCH -d '{"price":199.99}' https://api.example.com/api/warehouse/pricing/item001HTTP/1.1 204 No Content
Server: nginx/1.19.5
Connection: keep-alive
$ curl -iX PATCH -d@/etc/services https://api.example.com/api/warehouse/pricing/item001
HTTP/1.1 413 Request Entity Too Large
Server: nginx/1.19.5
Content-Type: application/json
Content-Length: 45
Connection: close
{"status":413,"message":"Payload too large"}Validation des corps de requête
[Note de la rédaction – Le cas d’utilisation suivant est l’un des nombreux cas d’utilisation du module JavaScript NGINX. Pour obtenir la liste complète, consultez Cas d’utilisation du module JavaScript NGINX.]
En plus d’être vulnérables aux attaques par dépassement de mémoire tampon provoquées par des corps de requête volumineux, les services d’API backend peuvent être affectés par des corps contenant des données non valides ou inattendues. Pour les applications qui exigent un corps de requête contenant du JSON correctement formaté, nous pouvons utiliser le module JavaScript NGINX<.htmla> pour vérifier que les données JSON sont analysées sans erreur avant de transmettre la requête au service d’API backend.
Une fois le module JavaScript installé, nous utilisons la directive js_import pour référencer le fichier contenant le code JavaScript de la fonction qui valide les données JSON.
Chargement de l’exemple de code…
La directive « js_set » définit une nouvelle variable, $json_validated, qui est évaluée en appelant la fonction parseRequestBody.
Chargement de l’exemple de code…
La fonction parseRequestBody tente d’analyser le corps de la requête à l’aide de la méthode JSON.parse (ligne 6). Si l’analyse réussit, le nom du groupe de serveurs en amont auquel cette requête est destinée est renvoyé (ligne 8). Si le corps de la requête ne peut pas être analysé (ce qui provoque une exception), l’adresse d’un serveur local est renvoyée (ligne 11). La directive return renseigne la variable $json_validated afin que nous puissions l’utiliser pour déterminer où envoyer la requête.
Chargement de l’exemple de code…
Dans la section de routage des URI de l’API Warehouse, nous modifions la directive proxy_pass à la ligne 22. Comme dans les configurations de l’API Warehouse présentées dans les sections précédentes, celle-ci transmet la requête au service d’API backend, mais utilise désormais la variable $json_validated comme adresse de destination. Si le corps de la requête du client a bien été analysé en tant que JSON, nous transmettons la requête au groupe de serveurs en amont défini à la ligne 15. En revanche, si une exception s’est produite, nous utilisons la valeur renvoyée 127.0.0.1:10415 pour envoyer une réponse d’erreur au client.
Chargement de l’exemple de code…
Lorsque des requêtes sont transmises par proxy à ce serveur virtuel, NGINX envoie au client la réponse 415 (Type de média non pris en charge).
Une fois cette configuration complète mise en place, NGINX ne transmet les requêtes au service d’API backend que si leur corps JSON est correctement formaté.
$ curl -iX POST -d '{"sku":"item002","price":85.00}' https://api.example.com/api/warehouse/pricingHTTP/1.1 201 Created
Server: nginx/1.19.5
Location: /api/warehouse/pricing/item002
$ curl -X POST -d 'item002=85.00' https://api.example.com/api/warehouse/pricing
{"status":415,"message":"Unsupported media type"}Remarque concernant la variable « $request_body »
La fonction JavaScript « parseRequestBody » utilise la variable « $request_body » pour analyser le contenu JSON. Cependant, NGINX ne renseigne pas cette variable par défaut et transmet simplement le corps de la requête au serveur backend sans effectuer de copies intermédiaires. En utilisant la directive « mirror » dans la section de routage des URI (ligne 16), nous créons une copie de la requête du client et renseignons ainsi la variable « $request_body ».
Chargement de l’exemple de code…
Les directives des lignes 17 et 19 contrôlent la manière dont NGINX traite le corps de la requête en interne. Nous définissons client_body_buffer_size sur la même taille que client_max_body_size afin que le corps de la requête ne soit pas écrit sur le disque. Cela améliore les performances globales en réduisant au minimum les opérations d’E/S sur le disque, mais au prix d’une utilisation accrue de la mémoire. Pour la plupart des cas d’utilisation d’une passerelle d’API avec des corps de requête de petite taille, il s’agit d’un bon compromis.
Comme indiqué précédemment, la directive mirror crée une copie de la requête du client. Cette copie ne nous servant qu’à renseigner $request_body, nous l’envoyons vers un emplacement « sans issue » (/_get_request_body) que nous définissons dans le bloc server de la configuration principale de la passerelle API.
Chargement de l’exemple de code…
Cet emplacement se contente d’envoyer la réponse 204 (Aucun contenu). Comme cette réponse est liée à une requête mise en miroir, elle est ignorée et n’ajoute donc qu’une surcharge négligeable au traitement de la requête client d’origine.
Résumé
Dans ce deuxième article de notre série consacrée au déploiement de NGINX Open Source et de NGINX Plus en tant que passerelle d’API, nous nous sommes penchés sur la difficulté de protéger les services d’API backend, dans un environnement de production, contre les clients malveillants ou au comportement inapproprié. NGINX utilise, pour gérer le trafic des API, la même technologie que celle qui permet d’alimenter et de protéger les sites Internet les plus fréquentés aujourd’hui.
Découvrez l’autre article de cette série :
- La première partie explique comment configurer NGINX pour certains cas d’usage essentiels d’une passerelle API.
- Partie 3 explique comment déployer NGINX en tant que passerelle API pour les services gRPC.
Pour essayer NGINX Plus comme passerelle d’API, commencez votre essai gratuit de 30 jours dès aujourd’hui ou contactez-nous pour discuter de vos cas d’utilisation. Pendant votre période d’essai, utilisez l’ensemble complet des fichiers de configuration disponibles dans notre dépôt GitHub Gist.
À propos de l’auteur

Articles de blog associés

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA
Découvrez les principaux enseignements tirés des réponses à l’enquête annuelle F5 NGINX sur les dernières tendances en matière de technologies cloud natives, de sécurité, d’ingénierie de plateforme et d’IA.
Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?
Lorsque de nouvelles CVE sont signalées, il est facile de ne plus savoir si elles concernent un outil de contrôleur Ingress NGINX particulier, car il existe plusieurs projets. Cet article présente les CVE et explique comment distinguer les différents projets de contrôleurs Ingress afin de dissiper toute confusion.
Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX
Votre API répond-elle aux requêtes en moins de 30 ms ? Si ce n’est pas le cas, il ne s’agit pas d’une API en temps réel offrant la faible latence exigée par la grande majorité des organisations. Mesurez la latence de votre API avec l’outil rtapi de NGINX et découvrez comment NGINX peut l’aider à offrir une réactivité en temps réel.

Un testeur d’expressions régulières pour NGINX et NGINX Plus
Le testeur d’expressions régulières (regex) pour NGINX et NGINX Plus vous évite de tâtonner en vous indiquant si une expression régulière utilisée dans un bloc location ou map correspond aux valeurs comme prévu. Il génère la configuration appropriée et affiche les valeurs des groupes de capture.
Caches partagés avec les clusters de cache NGINX Plus, partie 1
Optimisez la capacité de votre cache web en le répartissant entre plusieurs serveurs de cache web NGINX ou NGINX Plus. Découvrez comment procéder dans cet article.
Introduction aux microservices
Les microservices suscitent actuellement beaucoup d’intérêt. Cet article de blog est le premier d’une série de sept consacrée à la conception, au développement et au déploiement de microservices.