Al trabajar con lambdas, seguramente has llegado a un punto en donde debes pensar en cómo mejorar la seguridad desde diferentes frentes, uno de ellos es por ejemplo agregar autorización, otra opción puede ser agregar tus lambdas a una VPC y utilizar security groups, pero si has llegado este punto, seguro notarás que los security groups no tienen efecto en los API Gateway, esto básicamente por por que las lambdas son invocadas por medio del Lambda API Service Endpoint, por lo tanto las configuración de security groups funcionan solo con las configuración de salida pero no de entrada, en este punto una opción para realizar filtros por ip a un Gateway (y muchos filtros más) es utilizar un WAF, para ello podríamos plantear una arquitectura como la de la siguiente Figura
Que es AWS WAF?
Un WAF es básicamente un firewall a nivel de web, y al igual que en un firewall de red, podemos tener reglas, pero en este caso enfocadas a peticiones HTTP, con esto se pueden prevenir ataques utilizando diferentes filtros, AWS tiene un servicio especializados para la capa de aplicación y además tienes unos filtros preconfigurados que entre otras cosas interesantes contiene por ejemplo filtros precargados enfocados al cumplimiento del Top ten 10 OWASP sin realizar configuraciones, solo selecionado de las configuraciones precargadas, pero ademas se pueden realizar los propios filtros entre otros filtrar peticiones por IP, que es el objetivo de este post, veamos como configurar un WAF en AWS console
En la consola de WAF de AWS se debe crear un WAF seleccionando el tipo de recurso CloudFront el cual es el que usaremos
en las reglas selecion mi propia regla y configuro un IPset, básicamente un IPSet es un conjunto de ips con notación CIDR que puede ser ipv6 o ipv4
ya con esto se podría crear el WAF, sin embargo tenemos varias reglas predefinidas que nos podrían ayudar a filtrar peticiones de bots o intentos de vulnerar la seguridad de tu aplicación, esto se puede apreciar en la siguiente figura
APIGateWay V2
La gran pregunta, por que si el WAF es para el gateway, no se agrega el WAF directo? esto es debido a que HTTP API que es considerada la versión V2 de APIGateway no soporta WAF directamente, por ello se debe usar un CloudFront, para usar el V2 lo haré desde serverless con el siguiente código en el serverless config y el handler.py
service: mycognito-cognito-auth
frameworkVersion: '2'
variablesResolutionMode: 20210326
provider:
name: aws
runtime: python3.8
lambdaHashingVersion: 20201221
environment:
stage: ${opt:stage, 'dev'}
environment:
SECRET_VALUE: ${ssm:/secret/url}
httpApi:
authorizers:
MyProjectAuthorizer:
type: jwt
identitySource: $request.header.Authorization
issuerUrl: ${file(./config.${opt:stage, 'dev'}.json):ISSUER_URL}
audience:
- ${file(./config.${opt:stage, 'dev'}.json):AUDIENCE}
functions:
save:
handler: handler.hello
events:
- httpApi:
method: POST
path: /upload
authorizer:
name: MyProjectAuthorizer
import json
import os
def hello(event, context):
header_key = os.getenv("SECRET_VALUE")
body = {
"message": "Go Serverless v1.0! Your function executed successfully!",
"input": event,
"header_key":header_key
}
response = {
"statusCode": 200,
"body": json.dumps(body)
}
return response
como se aprecia en el código yml el evento es de tipo httpApi, con la versión 1 en la function se vería como en el siguiente código
functions:
save:
handler: handler.hello
events:
- http:
path: upload
method: post
el handler por el momento está agregando un header_key el cual es un valor que revisaremos más adelante cuando configuremos CloudFront. La versión 1 no tiene varios features importantes, entre ellos podríamos destacar la integración nativa con auth0, por ello es de vital importancia tener el WAF en la versión 2, desplegando el proyecto de serverless en AWS debería tener los siguiente
AWS CloudFront
Es un (CDN) content delivery network, básicamente es un servicio enfocado a la entrega de contenido al usuario final de forma rápida y segura utilizando beneficios del ruteo inteligente, el servicio de cloud front es muy usado para servir contenido estático, sin embargo también es usado para trabajar con APIS, que en nuestro caso es para lo que lo usaremos, para ello crearemos un distribución en AWS CloudFront con las siguientes configuraciones
La primera imagen hace referencia a la primera parte de la configuración, donde se pone a apuntar a la URL que genera el serverless y se agrega al header X-origin-Verify, valor que se usará como firma para saber que la petición viene desde CloudFront ya que el endpoint del GateWay sigue siendo público. La segunda imagen hace referencia al WAF que creamos anteriormente y que pondré a apuntar a la distribución que estoy creando, al crear tendremos el siguiente resultado
El domain name será la url que usaremos para acceder a nuestro endpoint, para ello después de que termine de desplegar puedo empezar a realizar pruebas, la primera configuración que realice, consistió bloquear mi ip con el WAF utilizando IPset, esto hace que si intento entrar a la url no debería permitirme el acceso
si remuevo la ip del WAF en IPSet y realizó una petición por post, ya me funciona correctamente
como se aprecia ya responde correctamente, ahora es de vital importancia que el lambda valide el x-origin-verify el cual es el valor que esta inyectando CloudFront, se puede hacer de muchas formas, pero para fines prácticos vamos a utilizar el mismo lambda para validarlo, voy a cambiar un poco el handler definido anteriormente, quedando de la siguiente forma
import json
import os
def hello(event, context):
try:
header_key = os.getenv("SECRET_VALUE")
headers = event["headers"]
origin_verify = headers.get("x-origin-verify")
if origin_verify and header_key == origin_verify:
body = {
"message": "Go Serverless v1.0! Your function executed successfully!",
"input": event,
"header_key":header_key
}
response = {
"statusCode": 200,
"body": json.dumps(body)
}
else:
response = {
"statusCode": 403,
}
return response
except Exception:
return {
"statusCode":500
}
con esta configuración despliego e intento hacer una petición directa el APIGateway, recuerden que este sigue publico, ya que conceptualmente está realizado para ser público
de esta forma aseguró que solamente las peticiones del CloudFront son permitidas, es una arquitectura base y se puede seguir mejorando por ejemplo yo podria proponer la siguiente mejora
Con esto se puede rotar el x-origin-verify para que sea diferente cada cierto tiempo, esto peor medio de un trigger que dispara AWS Secret Manager y se encarga de actualizar los valores
Conclusiones
La seguridad siempre sera necesaria en las aplicaciones, quizás no al principio pero eventualmente es importante conocer las opciones y elegir la que mas mejor se adapte a la arquitectura que se esta utilizando, siempre como recomendación configuraciones con VPC y Security Groups