BLOG | NGINX

Implementación de NGINX como API Gateway (Parte 1)

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

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

  • Esta publicación proporciona instrucciones de configuración detalladas para varios casos de uso. Publicado originalmente en 2018, 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.
  • La Parte 2 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 back-end en producción.
  • La Parte 3 explica cómo implementar NGINX Open Source y NGINX Plus como una puerta de enlace API para servicios gRPC.

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”.

En el corazón de las arquitecturas de aplicação modernas se encuentra la API HTTP. HTTP permite crear aplicações rápidamente y mantenerlas fácilmente. La API HTTP proporciona una interfaz común, independientemente de la escala de la aplicação, desde un microservicio de propósito único hasta un monolito integral. Al utilizar HTTP, los avances en la distribución de aplicação web que respaldan las propiedades de Internet a gran escala también se pueden utilizar para brindar una distribución de API confiable y de alto rendimiento.

Para obtener una excelente introducción a la importancia de las puertas de enlace de API para aplicações de microservicios, consulte Creación de microservicios: Usando una API Gateway en nuestro blog.

Como el principal proxy inverso y balanceador de carga liviano y de alto rendimiento, NGINX tiene las capacidades de procesamiento HTTP avanzadas necesarias para manejar el tráfico API. Esto hace que NGINX sea la plataforma ideal con la que construir una puerta de enlace API. En esta publicación de blog describimos una serie de casos de uso comunes de API Gateway y mostramos cómo configurar NGINX para manejarlos de una manera que sea eficiente, escalable y fácil de mantener. Describimos una configuración completa, que puede formar la base de una implementación de producción.

Presentación de la API de Warehouse

La función principal de la puerta de enlace API es proporcionar un punto de entrada único y consistente para múltiples API, independientemente de cómo se implementen o despliegan en el backend. No todas las API son aplicações de microservicios. Nuestra puerta de enlace API necesita administrar API, monolitos y aplicações existentes que atraviesan una transición parcial a microservicios.

En esta entrada del blog hacemos referencia a una API hipotética para la gestión de inventario, la “API de almacén”. Utilizamos un código de configuración de muestra para ilustrar diferentes casos de uso. La API de Warehouse es una API RESTful que consume solicitudes JSON y produce respuestas JSON. Sin embargo, el uso de JSON no es una limitación o requisito de NGINX cuando se implementa como puerta de enlace de API; NGINX es agnóstico al estilo arquitectónico y los formatos de datos utilizados por las propias API.

La API de Warehouse se implementa como una colección de microservicios discretos y se publica como una única API. Los recursos de inventario y precios se implementan como servicios separados y se despliegan en diferentes backends. Entonces, la estructura de ruta de la API es:    

API
└── almacén
├── inventario
└── precios

A modo de ejemplo, para consultar el inventario actual del almacén, una aplicação cliente realiza una solicitud HTTP GET a /api/warehouse/inventory .

Arquitectura de puerta de enlace API para múltiples aplicações

Organización de la configuración de NGINX

Una ventaja de usar NGINX como puerta de enlace API es que puede desempeñar esa función y al mismo tiempo actuar como proxy inverso, equilibrador de carga y servidor web para el tráfico HTTP existente. Si NGINX ya es parte de su pila de entrega de aplicação , generalmente no es necesario implementar una puerta de enlace API separada. Sin embargo, algunos de los comportamientos predeterminados esperados de una puerta de enlace API difieren de los esperados para el tráfico basado en navegador. Por ese motivo separamos la configuración de la puerta de enlace API de cualquier configuración existente (o futura) para el tráfico basado en navegador.

Para lograr esta separación, creamos un diseño de configuración que admite una instancia NGINX multipropósito y proporciona una estructura conveniente para automatizar la implementación de la configuración a través de canales CI/CD. La estructura de directorio resultante en /etc/nginx se ve así.

etc/
└── nginx/
├── api_conf.d/ ………………………………… Subdirectorio para la configuración por API
│ └── warehouse_api.conf …… Definición y política de la API de Warehouse
├── api_backends.conf ………………… Los servicios backend (upstreams)
├── api_gateway.conf …………………… Configuración de alto nivel para el servidor de API Gateway
├── api_json_errors.conf ………… Respuestas de error HTTP en formato JSON
├── conf.d/
│ ├── ...
│ └── existing_apps.conf
└── nginx.conf

Los directorios y nombres de archivos para todas las configuraciones de API Gateway tienen el prefijo api_ . Cada uno de estos archivos y directorios habilita una característica o capacidad diferente de la puerta de enlace API como se explica en detalle a continuación. El archivo warehouse_api.conf es un sustituto genérico de los archivos de configuración que se analizan a continuación y que definen la API de Warehouse de diferentes maneras.

Definición de la puerta de enlace API de nivel superior

Toda la configuración de NGINX comienza con el archivo de configuración principal, nginx.conf . Para leer la configuración de la puerta de enlace API, agregamos una directiva de inclusión en el bloque http en nginx.conf que hace referencia al archivo que contiene la configuración de la puerta de enlace, api_gateway.conf (línea 28 justo debajo). Tenga en cuenta que el archivo nginx.conf predeterminado utiliza una directiva de inclusión para extraer la configuración HTTP basada en el navegador desde el subdirectorio conf.d (línea 29). Esta publicación de blog hace un uso extensivo de la directiva include para facilitar la legibilidad y permitir la automatización de algunas partes de la configuración.

 

El archivo api_gateway.conf define el servidor virtual que expone NGINX como una puerta de enlace API para los clientes. Esta configuración expone todas las API publicadas por la puerta de enlace de API en un único punto de entrada, https://api.example.com/ (línea 9), protegido por TLS según la configuración en las líneas 12 a 17. Tenga en cuenta que esta configuración es puramente HTTPS: no hay ningún escucha HTTP de texto sin formato. Esperamos que los clientes API conozcan el punto de entrada correcto y realicen conexiones HTTPS de forma predeterminada.

Esta configuración está pensada para ser estática: los detalles de las API individuales y sus servicios de backend se especifican en los archivos a los que hace referencia la directiva include en la línea 20. Las líneas 23 a 26 tratan sobre el manejo de errores y se analizan en Respuesta a errores más adelante.

 

Servicio único vs. Backends de API de microservicios

Algunas API pueden implementarse en un solo backend, aunque normalmente esperamos que haya más de una, por razones de resiliencia o equilibrio de carga. Con las API de microservicios, definimos backends individuales para cada servicio; juntos funcionan como la API completa. Aquí, nuestra API de almacén se implementa como dos servicios separados, cada uno con múltiples backends.

 

Todos los servicios de API de backend, para todas las API publicadas por la puerta de enlace de API, se definen en api_backends.conf . Aquí utilizamos múltiples pares de direcciones IP y puertos en cada bloque ascendente para indicar dónde se implementa el código API, pero también se pueden usar nombres de host. Los suscriptores de NGINX Plus también pueden aprovechar el equilibrio de carga de DNS dinámico para que se agreguen nuevos backends a la configuración de tiempo de ejecución automáticamente.

Definición de la API de almacén

La API de almacén está definida por una serie de bloques de ubicación en una configuración anidada, como lo ilustra el siguiente ejemplo. El bloque de ubicación externa ( /api/warehouse ) identifica la ruta base, bajo la cual las ubicaciones anidadas especifican los URI válidos que se enrutan a los servicios de API de backend. El uso de un bloque externo nos permite definir políticas comunes que se aplican a toda la API (en este ejemplo, la configuración de registro en la línea 6).

 

NGINX tiene un sistema altamente eficiente y flexible para hacer coincidir la URI de la solicitud con una sección de la configuración. El orden de las directivas de ubicación no es importante: se elige la coincidencia más específica. Aquí, las ubicaciones anidadas en las líneas 10 y 14 definen dos URI que son más específicas que el bloque de ubicación externo; la directiva proxy_pass en cada bloque anidado enruta las solicitudes al grupo ascendente apropiado. La configuración de la política se hereda de la ubicación externa a menos que sea necesario proporcionar una política más específica para ciertas URI.

Cualquier URI que no coincida con una de las ubicaciones anidadas se maneja mediante la ubicación externa, que incluye una directiva general (línea 18) que devuelve la respuesta. 404(No encontrado) para todas las URI no válidas.

Elegir entre amplio y amplio Definición precisa de las API

Hay dos enfoques para la definición de API: amplio y preciso. El enfoque más adecuado para cada API depende de los requisitos de seguridad de la API y de si es deseable que los servicios de backend manejen URI no válidos.

En warehouse_api_simple.conf anterior, utilizamos el enfoque amplio para la API de Warehouse, definiendo prefijos de URI en las líneas 10 y 14 de modo que un URI que comience con uno de los prefijos se envíe por proxy al servicio de backend apropiado. Con esta amplia coincidencia de ubicación basada en prefijos, las solicitudes de API a los siguientes URI son todas válidas:

/api/almacén/inventario
/api/almacén/inventario/
/api/almacén/inventario/foo
/api/almacén/inventariofoo
/api/almacén/inventario/barra/

Si la única consideración es redirigir cada solicitud al servicio backend correcto, el enfoque amplio proporciona el procesamiento más rápido y la configuración más compacta. Por otro lado, un enfoque más preciso permite que la puerta de enlace de API comprenda el espacio URI completo de la API al definir explícitamente la ruta URI para cada recurso de API disponible. Adoptando un enfoque preciso, la siguiente configuración para el enrutamiento de URI en la API de Warehouse utiliza una combinación de coincidencia exacta ( = ) y expresiones regulares ( ~ ) para definir todos y cada uno de los URI válidos.

 

Esta configuración es más detallada, pero describe con mayor precisión los recursos implementados por los servicios de backend. Esto tiene la ventaja de proteger los servicios backend de solicitudes de clientes mal formadas, al costo de una pequeña sobrecarga adicional por la coincidencia de expresiones regulares. Con esta configuración establecida, NGINX acepta algunos URI y rechaza otros por considerarlos inválidos:

URI válidos   URI no válidas
/api/almacén/inventario   /api/almacén/inventario/
/api/almacén/inventario/estante/foo   /api/almacén/inventariofoo
/api/almacén/inventario/estante/foo/caja/barra   /api/almacén/inventario/estante
/api/almacén/inventario/estante/-/caja/-   /api/almacén/inventario/estante/foo/bar
/api/almacén/precios/baz   /api/almacén/precios
    /api/almacén/precios/baz/pub

El uso de una definición de API precisa permite que los formatos de documentación de API existentes impulsen la configuración de la puerta de enlace de API. Es posible automatizar las definiciones de API de NGINX desde la especificación OpenAPI (antes llamada Swagger). Entre los Gists de esta publicación del blog se incluye un ejemplo de guión para este propósito.

Reescritura de solicitudes de clientes para gestionar cambios importantes

A medida que las API evolucionan, a veces es necesario realizar cambios que rompen la estricta compatibilidad con versiones anteriores y requieren que los clientes se actualicen. Un ejemplo de ello es cuando se cambia el nombre o el movimiento de un recurso API. A diferencia de un navegador web, una puerta de enlace API no puede enviar a sus clientes una redirección (código301 (Mudado permanentemente) ) nombrando la nueva ubicación. Afortunadamente, cuando no es práctico modificar los clientes de API, podemos reescribir las solicitudes de los clientes sobre la marcha.

En el siguiente ejemplo, utilizamos el mismo enfoque amplio que en warehouse_api_simple.conf anterior, pero en este caso la configuración reemplaza una versión anterior de la API de Warehouse donde el servicio de precios se implementó como parte del servicio de inventario. La directiva de reescritura en la línea 3 convierte las solicitudes al antiguo recurso de precios en solicitudes al nuevo servicio de precios.

 

Respondiendo a los errores

Una de las diferencias clave entre las API HTTP y el tráfico basado en navegador es cómo se comunican los errores al cliente. Cuando NGINX se implementa como una puerta de enlace API, lo configuramos para que devuelva errores de la manera que mejor se adapte a los clientes API.

La configuración de la puerta de enlace API de nivel superior incluye una sección que define cómo manejar las respuestas de error.

 

El página de error La directiva en la línea 23 especifica que cuando una solicitud no coincide con ninguna de las definiciones de API, NGINX devuelve la 400 (Malo Pedido) error en lugar del valor predeterminado 404 (No Encontró) error. Este comportamiento (opcional) requiere que los clientes de API realicen solicitudes únicamente a los URI válidos incluidos en la documentación de API y evita que clientes no autorizados descubran la estructura URI de las API publicadas a través de la puerta de enlace de API.

La línea 24 se refiere a errores generados por los propios servicios de backend . Las excepciones no controladas pueden contener seguimientos de pila u otros datos confidenciales que no queremos que se envíen al cliente. Esta configuración agrega un nivel adicional de protección al enviar una respuesta de error estandarizada al cliente.

La lista completa de respuestas de error estandarizadas se define en un archivo de configuración separado al que hace referencia la directiva include en la línea 25, cuyas primeras líneas se muestran a continuación. Este archivo se puede modificar si se prefiere un formato de error distinto de JSON, y se puede cambiar el valor default_type en la línea 26 de api_gateway.conf para que coincida. También puede tener una directiva de inclusión separada en la sección de políticas de cada API para hacer referencia a un archivo diferente de respuestas de error que anulan las respuestas globales.

Con esta configuración establecida, una solicitud de cliente para una URI no válida recibe la siguiente respuesta. [terminal]$ curl -i https://api.example.com/foo HTTP/1.1 400 Bad Request Server: nginx/1.19.5 Content-Type: aplicação/json Content-Length: 39 Conexión: keep-alive {"status":400,"message":"Solicitud incorrecta"}[/terminal]

Implementación de la autenticación

Es inusual publicar API sin algún tipo de autenticación para protegerlas. NGINX ofrece varios enfoques para proteger las API y autenticar clientes de API. Para obtener información sobre los enfoques que también se aplican a las solicitudes HTTP normales, consulte la documentación de listas de control de acceso basadas en direcciones IP (ACL), autenticación de certificado digital y autenticación básica HTTP . Aquí nos centramos en los métodos de autenticación específicos de la API.

Autenticación de clave API

Las claves API son un secreto compartido conocido por el cliente y la puerta de enlace API. Una clave API es esencialmente una contraseña larga y compleja emitida al cliente API como credencial a largo plazo. Crear claves API es sencillo: simplemente codifique un número aleatorio como en este ejemplo.

$ openssl rand -base64 18 7B5zIqmRGXmrJTFmKa99vcit

En la línea 2 del archivo de configuración de la puerta de enlace de API de nivel superior, api_gateway.conf , incluimos un archivo llamado api_keys.conf , que contiene una clave de API para cada cliente de API, identificada por el nombre del cliente u otra descripción. Aquí está el contenido de ese archivo:

Las claves API se definen dentro de un bloque de mapa . La directiva de mapa toma dos parámetros. El primero define dónde encontrar la clave API, en este caso en el encabezado HTTP apikey de la solicitud del cliente, tal como se captura en la variable $http_apikey . El segundo parámetro crea una nueva variable ( $api_client_name ) y la establece en el valor del segundo parámetro en la línea donde el primer parámetro coincide con la clave.

Por ejemplo, cuando un cliente presenta la clave API 7B5zIqmRGXmrJTFmKa99vcit , la variable $api_client_name se establece en client_one . Esta variable se puede utilizar para verificar clientes autenticados e incluirse en entradas de registro para una auditoría más detallada. El formato del bloque de mapa es simple y fácil de integrar en los flujos de trabajo de automatización que generan el archivo api_keys.conf desde un almacén de credenciales existente.

Aquí habilitamos la autenticación de clave API modificando la configuración “amplia” ( warehouse_api_simple.conf ) para incluir una directiva auth_request en la sección de política que delega la decisión de autenticación a una ubicación específica.

 

Con la directiva auth_request (línea 7) podemos, por ejemplo, hacer que la autenticación sea manejada por un servidor de autenticación externo como OAuth 2.0 token introspection . En este ejemplo, en cambio, agregamos la lógica para validar las claves API al archivo de configuración de la puerta de enlace API de nivel superior , en la forma del siguiente bloque de ubicación llamado /_validate_apikey .

 

La directiva interna en la línea 30 significa que los clientes externos no pueden acceder directamente a esta ubicación (solo mediante auth_request ). Se espera que los clientes presenten su clave API en el encabezado HTTP apikey . Si este encabezado falta o está vacío (línea 32), enviamos un401 Respuesta (no autorizada) para indicarle al cliente que se requiere autenticación. La línea 35 maneja el caso en el que la clave API no coincide con ninguna de las claves en el bloque de mapa , en cuyo caso el parámetro predeterminado en la línea 2 de api_keys.conf establece $api_client_name en una cadena vacía, y enviamos un403 (Prohibido) Respuesta para avisarle al cliente que la autenticación falló. Si ninguna de esas condiciones coincide, la clave API es válida y la ubicación devuelve una 204Respuesta (sin contenido) .

Con esta configuración implementada, la API de Warehouse ahora implementa la autenticación de clave API.

$ curl https://api.example.com/api/warehouse/pricing/item001 {"estado":401,"mensaje":"No autorizado"} $ curl -H "apikey: thisIsInvalid" https://api.example.com/api/warehouse/pricing/item001 {"estado":403,"mensaje":"Prohibido"} $ curl -H "apikey: 7B5zIqmRGXmrJTFmKa99vcit" https://api.example.com/api/warehouse/pricing/item001 {"sku":"item001","price":179.99}

Autenticación JWT

Los tokens web JSON (JWT) se utilizan cada vez más para la autenticación de API. La compatibilidad nativa con JWT es exclusiva de NGINX Plus, lo que permite la validación de JWT como se describe en Autenticación de clientes API con JWT y NGINX Plus en nuestro blog. Para ver una implementación de ejemplo, consulte Controlar el acceso a métodos específicos en la Parte 2.

resumen

Este primer blog de una serie detalla una solución completa para implementar NGINX Open Source y NGINX Plus como puerta de enlace API. El conjunto completo de archivos analizados en este blog se puede revisar y descargar desde nuestro repositorio Gist de GitHub .

Echa un vistazo a las otras publicaciones de esta serie:

  • La Parte 2 explora casos de uso más avanzados para proteger los servicios backend de clientes maliciosos o con mal comportamiento.
  • La Parte 3 explica cómo implementar NGINX como una puerta de enlace API para servicios gRPC.

Para probar NGINX Plus, comience hoy su prueba gratuita de 30 días o contáctenos para analizar sus casos de uso.


"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.