¿Qué hace que un controlador de AWS Lambda sea resistente, rastreable y fácil de mantener? ¿Cómo se escribe un código de este tipo?
En esta serie de blogs, intentaré responder estas preguntas compartiendo mis conocimientos y las mejores prácticas de AWS Lambda, para que no cometa los mismos errores que yo cometí antes.
Esta serie de blogs presenta progresivamente las mejores prácticas y utilidades agregando una utilidad a la vez.
La parte 1 se centró en el registro.
La segunda parte se centró en la observabilidad: seguimiento y rastreo.
La parte 3 se centró en la observabilidad del dominio empresarial .
La parte 4 se centró en las variables de entorno .
La parte 6 se centró en la configuración y los indicadores de características.
La parte 7 se centró en cómo iniciar su propio servicio sin servidor en dos clics.
Parte 8 centrado en las mejores prácticas de AWS CDK .
Este blog se centra en las mejores prácticas de validación y análisis de entradas.
Proporcionaré un proyecto de plantilla de controlador AWS Lambda en Python, funcional y de código abierto.
Este controlador incorpora las mejores prácticas sin servidor y tiene todas las características necesarias para un controlador adecuado y listo para producción.
Durante esta serie de blogs, cubriré el registro, la observabilidad, la validación de entrada, los indicadores de características, la configuración dinámica y cómo usar variables de entorno de forma segura.
Si bien los ejemplos de código están escritos en Python, los principios son válidos para todos los lenguajes de programación compatibles con las funciones de AWS Lambda.
Puede encontrar todos los ejemplos en este repositorio de GitHub , incluido el código de implementación de CDK.
El caso de la validación de entrada
Los desarrolladores tienden a centrarse en implementar la lógica empresarial del controlador AWS Lambda y prestan menos atención a la validez del parámetro de entrada "evento".
Su algoritmo es simple: extrae la carga útil de la lógica empresarial de la entrada y la procesa. Fácil.
Sin embargo, este comportamiento excesivamente optimista puede provocar fallos, comportamientos indefinidos, errores e incluso problemas de seguridad.
En este blog aprenderá la importancia de la validación de entrada en la nube, los obstáculos que previene y cómo superar los desafíos inherentes y la complejidad que encuentra al desarrollar funciones de AWS Lambda.
Aprenderá cómo procesar la información de sus eventos de una manera segura y resiliente para que pueda concentrarse en las cosas que más importan: su lógica empresarial.
El enfoque optimista
Examinemos el siguiente código optimista del controlador AWS Lambda:
El controlador de AWS Lambda ' my_handler ' recibe el parámetro ' event ', un diccionario de Python.
La línea 6, que puede parecer inocente, es de hecho bastante peligrosa y demuestra varias suposiciones ocultas.
Se supone que:
El argumento del diccionario ' evento ' tiene una clave ' Registros ' con un valor de lista.
La lista tiene al menos dos elementos.
Cada elemento de la lista es un diccionario y el segundo elemento de la lista contiene una clave " nombre ".
' my_name ' es una cadena no vacía que representa un nombre válido.
Todos los valores son seguros y están desinfectados y no exponen el código a una amenaza de seguridad (XSS, inyecciones de entrada, etc., como se analiza aquí ).
Validación sintáctica
Los tres primeros supuestos están relacionados con el fallo en la validación sintáctica : no validar la sintaxis de los campos estructurados (JSON, Lista, etc.).
Estas suposiciones pueden provocar que se generen excepciones como ' KeyError ', ' TypeError ' o ' IndexError '.
¿Qué sucede si se genera una excepción y pasa desapercibida?
Bueno, el controlador Lambda se bloqueará sin ningún tipo de gracia. Además, si una API Gateway activa la función AWS Lambda, se devuelve un código de error HTTP 5XX al autor de la llamada y la experiencia del usuario se ve afectada, ya que se pierde el control sobre el mensaje de error.
¿Qué sucede si maneja un lote de registros de SQS y, después de procesar dos registros con éxito, el tercer registro genera una excepción no controlada? Es una verdadera lástima, ya que todo el lote (tanto los registros procesados como los no procesados) se devolverá a la cola, listo para ser procesado nuevamente (y fallar nuevamente), lo que le costará dinero por las invocaciones adicionales.
Validación semántica
Los supuestos cuarto y quinto están relacionados con la validación de restricciones de valor, también conocida como validación semántica.
¿Es "my_name" un nombre válido? ¿Es un valor que no está vacío? ¿Coincide con una expresión regular esperada? Quién sabe, no se comprueba en el ejemplo, pero se supone que está bien.
Esta suposición oculta puede dar lugar a comportamientos indefinidos y a errores o excepciones difíciles de depurar.
"Tu código alguna vez fallará , y eso está bien, siempre y cuando falle de la manera "correcta"".
El enigma del esquema de eventos de AWS
Cuando un servicio de AWS envía un evento que activa su función AWS Lambda, se agrega información de metadatos al evento y se encapsula la carga útil de la lógica empresarial.
A esta información de metadatos la llamaremos "sobre". El sobre contiene información valiosa, encabezados interesantes y los datos más importantes: la carga útil de la lógica empresarial que desea procesar.
Ahí es donde la cosa se complica.
Cada servicio de AWS tiene su propia estructura de sobre y puede encapsular la carga útil de la lógica empresarial de forma diferente. Algunos servicios la guardan como una cadena codificada, otros como un diccionario.
Todo es muy diferente y no siempre está bien documentado.
Esta capa de complejidad adicional debe abordarse antes de validar la entrada de la carga útil comercial.
Nuestro objetivo principal aquí es centrarnos en la lógica empresarial y no queremos preocuparnos por los diferentes esquemas de servicio de AWS. Queremos que el tipo de sobre sea lo más transparente posible.
Echemos un vistazo a las estructuras de eventos de varios servicios de AWS.
Puente de eventos de AWS
La carga útil de la lógica empresarial se envía como un diccionario en el campo " detalle ".
Todos los demás campos se consideran como el sobre.
Puerta de enlace API (REST)
En API Gateway, la carga útil de la lógica empresarial se envía como una cadena codificada JSON en el campo " cuerpo ".
SQS
Un evento SQS es una lista de registros. La carga útil de la lógica empresarial se envía como una cadena codificada en JSON en el campo " cuerpo " de cada registro interno.
El resultado final
Las variaciones e inconsistencias del esquema de servicio de AWS aumentan el esfuerzo de validación del esquema. Necesitamos entender qué esquema de servicio de AWS esperar, dónde encontrar la carga útil y cómo decodificarlo antes de poder validarlo.
La solución
" La validación de entrada debe aplicarse tanto a nivel sintáctico como semántico . - OWASP
Validaremos el evento entrante, extraeremos la carga útil comercial de entrada, la decodificaremos y la validaremos de acuerdo con un esquema predefinido. Este esquema verificará que existan todos los parámetros requeridos y que su tipo sea el esperado, y validará todas las restricciones de valores.
El esquema cubrirá validaciones tanto sintácticas como semánticas.
Todo esto se conseguirá con una sola línea de código .
Analizador de AWS Lambda Powertools
Utilizaremos una utilidad de análisis.
Tuve el placer de escribir y contribuir con la utilidad Parser a un fantástico proyecto en Github llamado AWS Lambda Powertools . Hemos utilizado este repositorio anteriormente en la serie de blogs (partes uno a tres) para registro, seguimiento y métricas.
La utilidad del analizador le ayudará a lograr una validación de siguiente nivel.
El motor del analizador es la excelente biblioteca Pydantic presentada en la parte 4 .
Primero, necesitamos definir nuestra carga útil de lógica empresarial como un esquema Pydantic.
Supongamos que ' my_handler ' procesa pedidos para clientes, un cliente a la vez.
Se espera un documento JSON que contenga los dos parámetros: ' my_name ' y ' order_item_count '. Definamos las validaciones semánticas y sintácticas:
' my_name ' - nombre del cliente, una cadena no vacía de hasta 20 caracteres.
' order_item_count ' - un entero positivo que representa la cantidad de artículos pedidos que ' my_name ' colocó.
Y el esquema Pydantic correspondiente:
Pydantic es una potente biblioteca de análisis que permite definir una validación personalizada en forma de funciones de "validación". Lea más sobre esto aquí .
Validación del tiempo mágico
Agreguemos la utilidad Parser a " my_handler " y usemos el esquema " Input ". Suponemos que el controlador se activa mediante una puerta de enlace de API de AWS, lo que significa que el sobre contendrá los campos de metadatos de la puerta de enlace de API de AWS.
Echemos un vistazo al código de validación:
En la línea 3, importamos la función ' parse ' y la excepción ' ValidationError' .
En la línea 4, importamos ' ApiGatewayEnvelope', que se correlaciona con el servicio de AWS que activa este controlador, AWS API Gateway.
En la línea 7, importamos el esquema ' Input ' que definimos en el paso anterior. Todos los esquemas de controladores se colocan en la carpeta ' service/handlers/schema '.
La magia sucede en la línea 14. Le indicamos al analizador que extraiga y valide nuestro modelo de esquema ' Entrada ' (el esquema de carga útil de lógica empresarial) del diccionario ' evento '.
También le indicamos que el evento que espera tiene una estructura de sobre que coincide con la de una API Gateway de AWS. El analizador devolverá una instancia de clase de datos válida del tipo ' Input '.
En la línea 18, el controlador puede procesar de forma segura la carga útil de la lógica empresarial. Por ejemplo, podemos acceder a " my_name " escribiendo "input.my_name".
En caso de un error de validación, se genera una excepción detallada.
Las excepciones de Pydantic contienen información detallada sobre por qué falló la validación y qué valores o campos provocaron la falla.
En este ejemplo, es mejor registrar la excepción y devolver un código de error de solicitud HTTP BAD (400).
¿Qué pasa con otros servicios de AWS?
El analizador admite los servicios de integración de AWS Lambda más comunes, incluidos SNS, SQS, Kinesis, S3, EventBridge, etc.
Lea más sobre esto aquí .
Poniéndolo todo junto
Agreguemos la utilidad Parser a las otras utilidades presentadas en esta serie de blogs: el registrador, el trazador, las métricas y el analizador de variables de entorno.
El controlador ahora se verá así:
En la línea 34, analizamos y validamos la entrada.
En la línea 35, registramos los detalles válidos de la solicitud. No registramos " my_name ", ya que se considera información de identificación personal.
En la línea 40, usamos la clase de datos analizados para enviar la entrada a la función de manejo de lógica empresarial interna.
Prácticas recomendadas para el manejo de excepciones de validación
La mejor práctica para manejar excepciones de validación es registrar la excepción y devolver de manera ordenada un error detallado al llamador.
La excepción de validación del analizador contendrá información detallada sobre los campos mal formados en el ejemplo anterior.
En la línea 36, si la entrada está mal formada, se captura una excepción y se registra.
Por ejemplo, si falta la clave ' my_name ' en el evento de entrada, el código imprimirá el siguiente registro de errores de la excepción de Pydantic:
"error: 1 error de validación para el campo Input\nmy_name\n requerido (tipo=value_error.missing)".
En la línea 38, se envía un estado de solicitud incorrecta HTTP al autor de la llamada.
Uso de parámetros de metadatos de envolvente
Este es un caso de uso avanzado.
Al utilizar la función " parse " con el parámetro de envolvente, no se puede acceder a los parámetros de envolvente analizados. Sin embargo, en algunos casos, estos parámetros contienen datos que le resultarán útiles.
Puede utilizar la utilidad Parser sin especificar el argumento de envolvente y analizar únicamente de acuerdo con el argumento del modelo.
Se le solicitará que proporcione un esquema de Pydantic COMPLETO que contenga el esquema de entrada de su modelo de negocio Y el parámetro de metadatos.
La forma más sencilla de hacerlo es crear una nueva clase que extienda una clase de modelo Parser existente (modelo de puerta de enlace API, modelo SQS, etc.) y agregar su esquema de carga útil de lógica empresarial.
Vea ejemplos detallados aquí .
¿Validación? ¡No sólo para la entrada!
Debes realizar la validación del esquema en cualquier objeto de diccionario que utilice tu controlador de AWS Lambda. Puede ser una respuesta de boto3, un archivo de configuración JSON, una respuesta HTTP de un servicio o cualquier objeto que puedas asignar a un esquema.
Más vale prevenir que curar.
Próximamente
Con esto concluye la quinta parte de la serie.
Únase a mí en la siguiente parte donde implemento configuración dinámica y marcas de características.