Utilizar AWS WAF en APIGateway, es esto posible?

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

Figura 1: Arquitectura para filtrar peticiones a un APIGateway

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

Figura 2: Configuración básica de AWS WAF

En la consola de WAF de AWS se debe crear un WAF seleccionando el tipo de recurso CloudFront el cual es el que usaremos

Figura 3: Configuración de ips a filtrar de AWS WAF

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

Figura 4: Resumen de configuración básica de AWS WAF

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

Figura 5: Reglas adicionales del WAF

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

Figura 6: Aplicación lambda desplegada

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

Figura 7: Configuraciones de CloudFront
Figura 8: Agregando WAF al CloudFront

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

Figura 9: Resumen del CloudFront

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

Figura 10: Restricción de acceso por bloqueo de IP

si remuevo la ip del WAF en IPSet y realizó una petición por post, ya me funciona correctamente

Figura 11: Petición a APIGateway por medio de CloudFront Funcionando 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

Figura 12 : Petición directa al APIGateway

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

Figura 13: Propuesta mejora de la arquitectura

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

Te gusto el post y quisieras poder aplicar lo aprendido?

Únete al equipo de Simetrik AQUI