En Introducción a Apache APISIX estudiamos los diferentes componentes de esta plataforma y las ventajas de uso que tiene. Además, vimos una manera de levantarlo de forma dockerizada y configuramos una ruta sencilla con el plugin openid-connect, integrando autenticación a través de Auth0.
En este post, vamos a trabajar con algunos plugins que nos permitirán añadir una capa de seguridad y personalización a nuestras rutas. Analizaremos dos formas de requerir autenticación, un procedimiento para revisar el tráfico de las solicitudes entrantes y un mecanismo para controlar cómo se gestionan las solicitudes HTTP.
Consumer y Consumer group en APISIX
Hay ocasiones en las que vamos a necesitar aplicar una serie de restricciones o configuraciones concretas a un usuario o cliente. Para ello, APISIX proporciona un objeto denominado consumer con el que seremos capaces de definir estas especificaciones. A lo largo de este post, se mostrarán algunos ejemplos.
Los elementos que emplearemos para configurar un consumer son el username y los plugins que serán consumidos por la ruta que lo requiera. Para cada consumer, cada uno de los plugins que configuremos en él podrá estar enfocado en solventar una necesidad distinta del resto (como en el caso de autenticación y seguridad) o, por el contrario, complementarse para trabajar en una necesidad común (por ejemplo, varios plugins basados en el control del tráfico).
Por otro lado, si bien podemos habilitar plugins en diferentes partes de la configuración del API Manager (como en la ruta, en el servicio o en el consumer), la prioridad máxima se establece en el consumer, de modo que si configuramos uno para un usuario o cliente específico, se aplicará lo que hayamos definido en el consumer.
Otras veces, necesitaremos emplear lo que se denominan grupos de consumidores, que se utilizan para extraer configuraciones de plugins de uso común (no enfocados a una única ruta concreta) y pueden vincularse directamente a un consumidor. Mediante su empleo, podemos definir cualquier número de plugins y aplicarlo en diferentes consumidores. Sin embargo, hay que tener en cuenta que aquella configuración que se establezca en el consumidor, aunque sea sobre un plugin que ya provenga de un grupo de consumidores aplicado al susodicho consumidor, prevalecerá y sobrescribirá la configuración.
Autenticación: Políticas de control de acceso
Una de las formas más rápidas y sencillas de añadir una capa de seguridad a nuestras aplicaciones es mediante la autenticación de los usuarios o clientes que pueden tener acceso a ellas. Para tener una idea de cómo podemos aplicar esto con APISIX, vamos a trabajar con dos plugins: basic-auth y key-auth.
Plugin basic-auth
Cuando se habla de autenticación, lo primero que se nos viene a la mente son unas credenciales de usuario y contraseña. A esto se le denomina Basic Access Authentication y consiste en el empleo de la cabecera HTTP de la forma “Authorization: Basic <credenciales>”, donde <credenciales> es el cifrado en Base64 de “<usuario>:<contraseña>”. Esto se puede aplicar de forma sencilla a nuestras rutas creando previamente un consumer que posea un username o nombre del mismo y un json con el plugin basic-auth que vamos a utilizar, introduciendo las credenciales como se muestra en el siguiente ejemplo:
Una vez creado el consumer y configurado el plugin en él, lo aplicamos a nuestra ruta simplemente especificando el plugin que queremos usar. Éste tomará por defecto la configuración que se haya definido en el consumer.
Ahora, únicamente necesitamos enviar las credenciales junto con nuestra solicitud. Para ello, tenemos dos formas de hacer la request, con el comando curl -u <usuario>:<contraseña>
o mediante Authorization: Basic <credenciales>
. Esto se debe a que curl -u
(o curl --user
) se utiliza para realizar solicitudes HTTP que requieren autenticación básica, encargándose de ejecutar el proceso de cifrar las credenciales en Base64 y mandarlas en la cabecera “Authorization”.
Si, por el contrario, no indicamos las credenciales, el resultado que obtendríamos sería el siguiente:
Plugin key-auth
Otro método para asegurarnos de que únicamente los usuarios que estén autorizados puedan acceder a ciertas rutas es utilizando el plugin key-auth y asignar una clave única a una ruta o servicio. Esta forma de autenticación es bastante más sencilla que la anterior, ya que no requiere de un usuario o cliente asociado a unas credenciales. Para aplicarla, configuramos el consumer de la siguiente manera:
Este plugin posee tres parámetros customizables:
- header: Nombre de la cabecera desde donde se obtendrá la clave.
- query: La cadena de consulta de la que obtener la clave, aunque posee una prioridad inferior al
header
. - hide_credentials: Aquí determinaremos si se pasará la cabecera de la solicitud que contiene la información de autenticación o si se eliminará antes de llegar al proxy.
Nosotros vamos a trabajar con la cabecera header, que por defecto posee el nombre de «apikey», y le daremos un par de nombres diferentes para probarla: uno que podría estar reservado o uno de carácter personal.
Probamos la cabecera con la clave de la siguiente manera y obtenemos el resultado esperado:
Si tenemos la necesidad de cambiar el nombre de la cabecera a uno más personalizado, simplemente habría que especificar el nombre en la configuración del plugin en la ruta de la siguiente forma:
Primero, comprobamos que el nombre de cabecera que habíamos definido ya no se puede usar:
Y, por otro lado, empleamos el nuevo y vemos que funciona:
Hasta este punto, hemos comparado dos plugins de autenticación relativamente sencillos, enfocados a dos propósitos distintos. Con basic-auth fuimos capaces de dotar de credenciales a un usuario o cliente concretos y mediante key-auth hemos podido proporcionar una clave de uso común para asegurar el acceso a una ruta concreta de una forma flexible gracias a la posibilidad de crear nuestra propia cabecera. Esto resultaría muy útil a la hora de combinar distintos métodos de autenticación que pudiesen requerir del empleo de la cabecera «Authorization», que es bastante estándar.
Tráfico: Configuración para controlar las solicitudes
Plugin limit-req
Para proteger nuestros servicios de sobrecargas por solicitudes excesivas, vamos implementar el plugin limit-req, que se encarga de establecer el límite de solicitudes que pueden realizarse en un periodo de tiempo. Está construido empleando el algoritmo leaky bucket, que se basa en una analogía sobre cómo un cubo con fuga está siendo llenado con agua. El cubo representa a un servidor que está recibiendo peticiones (gotas de agua entrantes) a una velocidad, mientras que el procesamiento de las mismas (goteo por fuga) tiene lugar.
El objetivo del algoritmo con la analogía sería determinar la cantidad y velocidad óptimas de solicitudes entrantes que están permitidas en el servidor en base a su capacidad de procesamiento para no llegar al desbordamiento.
A diferencia de los plugins anteriores, este posee un repertorio mayor de parámetros de configuración. Para no extendernos demasiado, nos vamos a centrar en los siguientes:
- rate: Límite de solicitudes por segundo admitidas.
- burst: Número de peticiones adicionales que se permite retrasar por segundo.
- reject_code: Código de estado HTTP que devuelve en caso de exceder el límite impuesto.
- key_type: Tipo de clave que será usada y que se especifica por el usuario.
- key: Clave sobre la que se basará la limitación de la solicitud. En este caso, como es
“key_type”: ”var”
en lugar de“var_combination”
, la clave se tratará como un nombre de variable.
A continuación, le daremos los siguientes valores al plugin:
Estos valores indican las siguientes características:
- rate: 1. Implica que sólo se admitirá una solicitud por segundo.
- burst: 1. Únicamente se retrasará una solicitud por segundo para poder ser resuelta cuando el servidor se libere.
- rejected_code: 503. Es un código de error que indica que el servicio no se encuentra disponible en el momento de la consulta.
- key_type: var. Indica que la limitación de la solicitud dependerá del empleo de una variable que se especificará como un nombre de variable definido por el parámetro
“key”
. - key: remote_addr. Establece que la limitación se realizará en base a la dirección IP remota.
Para probar que lo hemos configurado bien, recomendamos emplear varias pestañas de la consola y realizar la solicitud numerosas veces de manera muy rápida. Recordemos que disponemos de un tiempo de un segundo entre solicitudes, teniendo en cuenta que la segunda de ellas esperará durante un segundo hasta ser atendida y es la tercera la que debería dar error si lo hemos hecho lo suficientemente rápido. Una herramienta que podría ser útil y rápida es la consola Terminator, que nos permite abrir muchas terminales en una única ventana, o si se prefiere, también puede hacerse un pequeño script.
A continuación, vemos el resultado de una petición que no alcanzó el límite y otra que sí lo ha hecho:
Ahora, vamos a aprovechar la lógica de nuestro consumidor cambiando el parámetro “key: consumer_name”
para aplicarlo. En este caso, nuestro consumer tiene definido el plugin key-auth con la configuración de la cabecera “Authorization” y la clave “cleverkey”.
Nuevamente, probamos a realizar solicitudes dentro y fuera del límite y obtenemos los siguientes dos resultados:
La utilidad de la configuración que hemos realizado con el plugin limit-req es lidiar con el uso excesivo de solicitudes que un cliente pueda realizar para tirar nuestra página web. Por otro lado, mitiga ataques de fuerza bruta y optimiza los costes a las empresas en infraestructuras cloud. De esta manera, protegemos el rendimiento de nuestras aplicaciones manteniendo así un flujo controlado de las solicitudes que se reciben.
Seguridad: Establecer políticas para proteger las rutas
Políticas cors
Para realizar solicitudes de recursos a un servidor web desde un dominio distinto a aquel del que procede el recurso, podemos aplicar políticas CORS (Cross-Origin Resource Sharing). De esta manera, podemos indicar qué especificaciones debe proporcionar en su solicitud el origen: dominio, esquema, puerto o métodos permitidos. Para ello, se puede utilizar el plugin cors, que permite definir reglas como los métodos y cabeceras permitidas.
Al igual que el plugin que empleamos en el punto anterior, este posee un amplio repertorio de parámetros de configuración. En este caso, nosotros emplearemos allow_methods (métodos permitidos para el protocolo HTTP), allow_headers (cabeceras permitidas en la solicitud) y max_age (tiempo máximo en segundos de almacenamiento del resultado en la caché).
Estos valores indican las siguientes características:
- allow_methods: GET. Establece que sólo podrá realizarse una solicitud si se emplea el método GET del protocolo HTTP.
- allow_headers: Content-Type: text/plain. Únicamente se podrá emplear la cabecera de texto plano en la solicitud.
- max_age: 1. El resultado será almacenado en la caché durante un segundo.
A continuación, vamos a realizar una solicitud aplicando correctamente las reglas predefinidas y vemos que obtenemos el resultado de forma exitosa:
Sin embargo, si empleamos un método del protocolo HTTP no permitido, como por ejemplo PUT, el resultado que obtendremos será “Method Not Allowed”:
Por otra parte, si aplicamos una cabecera no permitida, tampoco podremos acceder a la ruta. En ese caso, APISIX devolverá el error “400 Bad Request”:
Conclusión
En este artículo hemos trabajado con varios de los plugins de Apache APISIX para proteger nuestras rutas de accesos no autorizados y ataques maliciosos. Con ello, logramos mejorar la seguridad, el rendimiento y el buen funcionamiento de nuestras aplicaciones.
Sin embargo, para algunos de estos plugins nos hemos dejado en el tintero un conjunto muy interesante de parámetros configurables. Así que te invito a que juegues un poco con ellos para que puedas sacarle más partido a APISIX a la hora de configurar tus rutas.
¡Espero que te haya servido el contenido de este post!