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.
La Parte 1 proporciona instrucciones de configuración detalladas para varios casos de uso.
Esta publicación amplía esos casos de uso y analiza una variedad de medidas de seguridad que se pueden aplicar para proteger y asegurar los servicios de API de backend en producción:
Publicada originalmente en 2018, esta publicación se ha actualizado para reflejar las mejores prácticas actuales para la configuración de API, utilizando bloques de ubicación
anidados para enrutar solicitudes en lugar de reescribir reglas.
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”.
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.
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ículo001PATCH
/api/warehouse/pricing/item001
cambia el precio del artículo 001Podemos 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"}
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 .
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"}
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.
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"}
[ 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"}
$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.
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:
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.