BLOG | NGINX

Implementación de NGINX como API Gateway, parte 2: Protección de los servicios backend

NGINX - Parte de F5 - horizontal, negro, tipo RGB
Miniatura de Liam Crilly
Liam Crilly
Publicado el 20 de enero de 2021

Esta es la segunda publicación de blog de nuestra serie sobre la implementación de NGINX Open Source y NGINX Plus como puerta de enlace de API.

Nota : Salvo que se indique lo contrario, toda la información de esta publicación se aplica tanto a NGINX Open Source como a NGINX Plus. Para facilitar la lectura, el resto del blog se refiere simplemente a “NGINX”.

Limitación de velocidad

A diferencia de los clientes basados en navegador, los clientes API individuales pueden colocar enormes cargas en sus API, incluso hasta el punto de consumir una proporción tan alta de recursos del sistema que otros clientes API quedan efectivamente bloqueados. No sólo los clientes maliciosos plantean esta amenaza: un cliente de API con mal comportamiento o con errores podría entrar en un bucle que sature el backend. Para protegernos contra esto, aplicamos un límite de tarifa para garantizar un uso justo por parte de cada cliente y proteger los recursos de los servicios de backend.

NGINX puede aplicar límites de velocidad basados en cualquier atributo de la solicitud. Normalmente se utiliza la dirección IP del cliente, pero cuando la autenticación está habilitada para la API, el ID del cliente autenticado es un atributo más confiable y preciso.

Los límites de velocidad en sí se definen en el archivo de configuración de la puerta de enlace API de nivel superior y luego se pueden aplicar de manera global, por API o incluso por URI.

 

En este ejemplo, la directiva limit_req_zone en la línea 4 define un límite de velocidad de 10 solicitudes por segundo para cada dirección IP de cliente ( $binary_remote_addr ), y la de la línea 5 define un límite de 200 solicitudes por segundo para cada ID de cliente autenticado ( $http_apikey ). Esto ilustra cómo podemos definir múltiples límites de velocidad independientemente de dónde se apliquen. Una API puede aplicar múltiples límites de velocidad al mismo tiempo o aplicar diferentes límites de velocidad para diferentes recursos.

Luego, en el siguiente fragmento de configuración, usamos la directiva limit_req para aplicar el primer límite de velocidad en la sección de política de la “API de almacén” descrita en la Parte 1<.htmla>. De forma predeterminada, NGINX envía el 503 (Servicio Indisponible) Respuesta cuando se ha excedido el límite de velocidad. Sin embargo, es útil que los clientes API sepan explícitamente que han excedido su límite de velocidad, para que puedan modificar su comportamiento. Para este fin, utilizamos la directiva limit_req_status para enviar el 429(Demasiadas solicitudes ) respuesta en su lugar.

 

Puede utilizar parámetros adicionales a la directiva limit_req para ajustar la forma en que NGINX aplica los límites de velocidad. Por ejemplo, es posible poner en cola las solicitudes en lugar de rechazarlas directamente cuando se excede el límite, lo que da tiempo para que la tasa de solicitudes caiga por debajo del límite definido. Para obtener más información sobre cómo ajustar los límites de velocidad, consulte Limitación de velocidad con NGINX y NGINX Plus en nuestro blog.

Aplicación de métodos de solicitud específicos

Con las API RESTful, el método HTTP (o verbo) es una parte importante de cada llamada API y muy significativo para la definición de API. Tomemos como ejemplo el servicio de precios de nuestra API de almacén:

  • GET /api/warehouse/pricing/item001 devuelve el precio del artículo001
  • PATCH /api/warehouse/pricing/item001 cambia el precio del artículo 001

Podemos actualizar las definiciones de enrutamiento de URI en la API de Warehouse para aceptar solo estos dos métodos HTTP en las solicitudes al servicio de precios (y solo el método GET en las solicitudes al servicio de inventario).

 

Con esta configuración establecida, las solicitudes al servicio de precios que utilizan métodos distintos a los enumerados en la línea 22 (y al servicio de inventario que no sea el de la línea 13) se rechazan y no se pasan a los servicios de back-end. NGINX envía el 405(Método no permitido) respuesta para informar al cliente de API sobre la naturaleza precisa del error, como se muestra en el siguiente seguimiento de la consola. Cuando se requiere una política de seguridad de divulgación mínima, se puede utilizar la directiva error_page para convertir esta respuesta en un error menos informativo, por ejemplo400 ( Solicitud incorrecta ) .

$ 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":"Método no permitido"}

Aplicación de un control de acceso de grano fino

La parte 1 de esta serie describe cómo proteger las API del acceso no autorizado habilitando opciones de autenticación como claves API y tokens web JSON (JWT) . Podemos utilizar la ID autenticada, o los atributos de la ID autenticada, para realizar un control de acceso detallado.

Aquí mostramos dos ejemplos de este tipo.

Por supuesto, otros métodos de autenticación son aplicables a estos casos de uso de muestra, como la autenticación básica HTTP y la introspección de token OAuth 2.0 .

Controlar el acceso a recursos específicos

Digamos que queremos permitir que solo los “clientes de infraestructura” accedan al recurso de auditoría del servicio de inventario de Warehouse API. Con la autenticación de clave API habilitada, utilizamos un bloque de mapa para crear una lista de nombres de clientes de infraestructura permitidos para que la variable $is_infrastructure se evalúe como1 cuando se utiliza una clave API correspondiente.

 

En la definición de la API de Warehouse, agregamos un bloque de ubicación para el recurso de auditoría de inventario ( líneas 15 a 20 ). El bloque if garantiza que solo los clientes de infraestructura puedan acceder al recurso.

 

Tenga en cuenta que la directiva de ubicación en la línea 15 utiliza el modificador = (signo igual) para realizar una coincidencia exacta en el recurso de auditoría . Las coincidencias exactas tienen prioridad sobre las definiciones de prefijo de ruta predeterminadas utilizadas para los demás recursos. El siguiente seguimiento muestra cómo con esta configuración implementada, un cliente que no está en la lista blanca no puede acceder al recurso de auditoría de inventario. La clave API que se muestra pertenece a client_two (como se define en la Parte 1 ).

$ curl -H "clave de API: QzVV6y1EmQFbbxOfRCwyJs35" https://api.example.com/api/warehouse/inventory/audit {"estado":403,"mensaje":"Prohibido"}

Controlar el acceso a métodos específicos

Como se definió anteriormente , el servicio de precios acepta los métodos GET y PATCH , que permiten respectivamente a los clientes obtener y modificar el precio de un artículo específico. (También podríamos optar por permitir los métodos POST y DELETE , para proporcionar una gestión completa del ciclo de vida de los datos de precios). En esta sección, ampliamos ese caso de uso para controlar qué métodos pueden emitir usuarios específicos. Con la autenticación JWT habilitada para la API de Warehouse, los permisos para cada cliente se codifican como reclamos personalizados. Los JWT emitidos a los administradores que están autorizados a realizar cambios en los datos de precios incluyen la reclamación "admin":true . Ahora ampliamos nuestra lógica de control de acceso para que sólo los administradores puedan realizar cambios.

 

Este bloque de mapa , agregado al final de api_gateway.conf , toma el método de solicitud ( $request_method ) como entrada y produce una nueva variable, $admin_permitted_method . Los métodos de solo lectura siempre están permitidos (líneas 62 a 64), pero el acceso a las operaciones de escritura depende del valor del reclamo de administración en el JWT (línea 65). Ahora ampliamos nuestra configuración de API de Warehouse para garantizar que solo los administradores puedan realizar cambios de precios.

 

La API de Warehouse requiere que todos los clientes presenten un JWT válido (línea 7). También verificamos que las operaciones de escritura estén permitidas evaluando la variable $admin_permitted_method (línea 25). Tenga en cuenta nuevamente que la autenticación JWT es exclusiva de NGINX Plus.

Control del tamaño de las solicitudes

Las API HTTP comúnmente utilizan el cuerpo de la solicitud para contener instrucciones y datos para que el servicio de API de backend los procese. Esto es cierto tanto para las API XML/SOAP como para las API JSON/REST. En consecuencia, el cuerpo de la solicitud puede representar un vector de ataque para los servicios de API de backend, que pueden ser vulnerables a ataques de desbordamiento de búfer al procesar cuerpos de solicitud muy grandes.

De forma predeterminada, NGINX rechaza solicitudes con cuerpos mayores a 1 MB. Esto se puede aumentar para las API que tratan específicamente con cargas útiles grandes, como el procesamiento de imágenes, pero para la mayoría de las API establecemos un valor más bajo.

 

La directiva client_max_body_size en la línea 7 limita el tamaño del cuerpo de la solicitud. Con esta configuración establecida, podemos comparar el comportamiento de la API Gateway al recibir dos solicitudes PATCH diferentes al servicio de precios. El primer comando curl envía una pequeña porción de datos JSON, mientras que el segundo comando intenta enviar el contenido de un archivo grande ( /etc/services ).

$ curl -iX PATCH -d '{"price":199.99}' https://api.example.com/api/warehouse/pricing/item001 HTTP/1.1 204 Sin contenido Servidor: nginx/1.19.5 Conexión: keep-alive $ curl -iX PATCH -d@/etc/services https://api.example.com/api/warehouse/pricing/item001 HTTP/1.1 413 Entidad de solicitud demasiado grande Servidor: nginx/1.19.5 Tipo de contenido: aplicação/json Longitud del contenido: 45 Conexión: cerrada {"status":413,"message":"Carga útil demasiado grande"}

Validación de cuerpos de solicitud

[ Editor : el siguiente caso de uso es uno de varios para el módulo JavaScript de NGINX. Para obtener una lista completa, consulte Casos de uso del módulo JavaScript NGINX . ]

Además de ser vulnerables a ataques de desbordamiento de búfer con cuerpos de solicitud grandes, los servicios de API de backend pueden ser susceptibles a cuerpos que contienen datos no válidos o inesperados. Para las aplicações que requieren JSON con el formato correcto en el cuerpo de la solicitud, podemos usar el módulo JavaScript de NGINX <.htmla> para verificar que los datos JSON se analicen sin errores antes de enviarlos al servicio de API de backend.

Con el módulo JavaScript instalado , usamos la directiva js_import para referenciar el archivo que contiene el código JavaScript para la función que valida los datos JSON.

 

La directiva js_set define una nueva variable, $json_validated , que se evalúa llamando a la función parseRequestBody .

 

La función parseRequestBody intenta analizar el cuerpo de la solicitud utilizando el método JSON.parse (línea 6). Si el análisis tiene éxito, se devuelve el nombre del grupo ascendente previsto para esta solicitud (línea 8). Si no se puede analizar el cuerpo de la solicitud (lo que provoca una excepción), se devuelve una dirección de servidor local (línea 11). La directiva de retorno rellena la variable $json_validated para que podamos usarla para determinar dónde enviar la solicitud.

 

En la sección de enrutamiento de URI de la API de Warehouse, modificamos la directiva proxy_pass en la línea 22. Pasa la solicitud al servicio de API de backend como en las configuraciones de API de Warehouse analizadas en secciones anteriores, pero ahora utiliza la variable $json_validated como dirección de destino. Si el cuerpo del cliente se analizó correctamente como JSON, entonces lo redirigimos al grupo ascendente definido en la línea 15. Sin embargo, si hubo una excepción, usamos el valor devuelto de 127.0.0.1:10415 para enviar una respuesta de error al cliente.

 

Cuando las solicitudes se envían a este servidor virtual, NGINX envía el 415( Tipo de medio no compatible ) Respuesta al cliente.

Con esta configuración completa, NGINX envía solicitudes al servicio API de backend solo si tienen cuerpos JSON con el formato correcto.

$ curl -iX POST -d '{"sku":"item002","price":85.00}' https://api.example.com/api/warehouse/pricing HTTP/1.1 201 Servidor creado: nginx/1.19.5 Ubicación: /api/warehouse/pricing/item002 $ curl -X POST -d 'item002=85.00' https://api.example.com/api/warehouse/pricing {"status":415,"message":"Tipo de medio no compatible"}

Una nota sobre la variable $request_body

La función de JavaScript parseRequestBody utiliza la variable $request_body para realizar el análisis de JSON. Sin embargo, NGINX no completa esta variable de forma predeterminada y simplemente transmite el cuerpo de la solicitud al backend sin hacer copias intermedias. Al utilizar la directiva mirror dentro de la sección de enrutamiento URI (línea 16), creamos una copia de la solicitud del cliente y, en consecuencia, completamos la variable $request_body .

 

Las directivas en las líneas 17 y 19 controlan cómo NGINX maneja el cuerpo de la solicitud internamente. Establecemos client_body_buffer_size en el mismo tamaño que client_max_body_size para que el cuerpo de la solicitud no se escriba en el disco. Esto mejora el rendimiento general al minimizar las operaciones de E/S de disco, pero a expensas de una utilización adicional de memoria. Para la mayoría de los casos de uso de API gateway con cuerpos de solicitud pequeños, este es un buen compromiso.

Como se mencionó, la directiva mirror crea una copia de la solicitud del cliente. Aparte de completar $request_body , no necesitamos esta copia, por lo que la enviamos a una ubicación “sin salida” ( /_get_request_body ) que definimos en el bloque del servidor en la configuración del portal API de nivel superior.

 

Esta ubicación no hace nada más que enviar el 204 (No Contenido) respuesta. Debido a que esta respuesta está relacionada con una solicitud reflejada, se ignora y, por lo tanto, agrega una sobrecarga insignificante al procesamiento de la solicitud del cliente original.

resumen

En esta segunda publicación de blog de nuestra serie sobre la implementación de NGINX Open Source y NGINX Plus como puerta de enlace de API, nos centramos en el desafío de proteger los servicios de API de backend en un entorno de producción de clientes maliciosos y con mal comportamiento. NGINX utiliza la misma tecnología para administrar el tráfico API que se utiliza para impulsar y proteger los sitios más activos de Internet hoy en día .

Echa un vistazo a la otra publicación de esta serie:

  • La Parte 1 explica cómo configurar NGINX en algunos casos de uso esenciales de API Gateway.
  • La Parte 3 explica cómo implementar NGINX como una puerta de enlace API para servicios gRPC.

Para probar NGINX Plus como puerta de enlace API, comience hoy su prueba gratuita de 30 días o contáctenos para analizar sus casos de uso . Durante su prueba, utilice el conjunto completo de archivos de configuración de nuestro repositorio GitHub Gist .


"Esta publicación de blog puede hacer referencia a productos que ya no están disponibles o que ya no reciben soporte. Para obtener la información más actualizada sobre los productos y soluciones F5 NGINX disponibles, explore nuestra familia de productos NGINX . NGINX ahora es parte de F5. Todos los enlaces anteriores de NGINX.com redirigirán a contenido similar de NGINX en F5.com.