Agregue encabezados de seguridad con Lambda@Edge y Terraform en AWS S3/CloudFront

Publicado: 2021-04-24

Cuando inicia sesión en https://app.sendgrid.com para consultar los detalles de su cuenta o visita https://mc.sendgrid.com para campañas de marketing, está visitando nuestras aplicaciones web front-end alojadas en depósitos de AWS S3 con Distribuciones de CloudFront encima de ellos.

Nuestras aplicaciones web consisten esencialmente en archivos JavaScript y CSS minificados y divididos en código, archivos HTML y archivos de imagen cargados en depósitos S3 como cachés de CloudFront y los entrega a nuestros usuarios. Cada uno de los entornos de nuestra aplicación web, que van desde la prueba, la puesta en escena y la producción, tiene un depósito de S3 y una distribución de CloudFront separados.

Esta infraestructura de AWS S3 y CloudFront funciona bien para nuestras aplicaciones web a escala en el alojamiento de archivos a través de una red de entrega de contenido, pero nuestras configuraciones iniciales carecían de protecciones más estrictas en forma de encabezados de seguridad.

Agregar estos encabezados de seguridad evitaría que los usuarios sufran ataques, como secuencias de comandos entre sitios, detección de MIME, secuestro de clics, inyección de código y ataques de intermediarios relacionados con protocolos inseguros. Si no se atienden, tendrían graves consecuencias para los datos de nuestros clientes y para la confianza de nuestra empresa en poder brindar una experiencia segura en la web.

Antes de investigar cómo agregar estos encabezados, primero dimos un paso atrás para ver dónde estábamos. Después de ejecutar la URL de nuestra aplicación web a través de un sitio web de escaneo de encabezados de seguridad , como era de esperar, recibimos una calificación reprobatoria, pero vimos una lista útil de encabezados para analizar, como se muestra a continuación.

Como puede ver, había mucho margen de mejora. Investigamos cómo configurar nuestros recursos de AWS S3 y CloudFront para responder con encabezados de seguridad para mitigar los riesgos y vulnerabilidades mencionados.

En un alto nivel, podemos lograr esto mediante la creación de una función Lambda@Edge que altere los encabezados de respuesta de origen para agregar los encabezados de seguridad deseados antes de que los archivos de la aplicación web regresen al navegador del usuario.

La estrategia es probar primero la conexión manual de las cosas a través de la consola de AWS. Luego, pondremos estas configuraciones en Terraform para guardar esta parte de la infraestructura en código para futuras referencias y compartibilidad entre otros equipos y aplicaciones.

¿Qué tipo de encabezados de seguridad nos gustaría agregar?

Como parte de las recomendaciones de nuestro equipo de seguridad de productos, se nos encomendó agregar encabezados de seguridad como "Seguridad de transporte estricta" y "Opciones de marco X". Le recomendamos que también consulte recursos como la hoja de trucos de MDN Web Security para ponerse al día. Aquí hay un breve resumen de los encabezados de seguridad que puede aplicar a sus aplicaciones web.

Seguridad estricta en el transporte (HSTS)

Esto es para proporcionar sugerencias al navegador para acceder a su aplicación web a través de HTTPS en lugar de HTTP.

Política de seguridad de contenido (CSP)

Esto es para establecer listas de permitidos explícitas sobre qué tipo de recursos carga o conecta en su aplicación web, como secuencias de comandos, imágenes, estilos, fuentes, solicitudes de red e iframes. Esta fue la más difícil de configurar para nosotros, ya que teníamos secuencias de comandos, imágenes, estilos y extremos de API de terceros para registrar explícitamente en estas políticas.

Sugerencia: use el encabezado Content-Security-Policy-Report-Only para ayudarlo con las pruebas en ciertos entornos. Si ciertos recursos violaban las políticas, observamos resultados útiles en la consola de los recursos que necesitábamos permitir en nuestras políticas.

Si desea evitar un percance divertido con pantallas en blanco y aplicaciones web que no se cargan, le recomendamos encarecidamente que primero experimente con sus políticas en modo de solo informe y realice pruebas exhaustivas antes de sentirse lo suficientemente seguro como para implementar estas políticas de seguridad en producción.

Opciones de tipo de contenido X

Esto es para mantener y cargar activos con tipos MIME correctos en su página web.

Opciones de marco X

Esto es para proporcionar reglas sobre cómo su aplicación web se carga potencialmente en un iframe.

Protección X-XSS

Esto evita que las páginas se carguen si se detecta un ataque de secuencias de comandos entre sitios en ciertos navegadores.

Política de referencia

Esto administra cómo el encabezado "Referente" con información sobre el origen de la solicitud pasa cuando se siguen enlaces a sitios o recursos externos.

Con estos encabezados de seguridad en mente, volvamos a cómo configuramos nuestras distribuciones de CloudFront hoy y cómo las funciones de Lambda@Edge nos ayudarán a lograr nuestro objetivo.

Uso de Lambda@Edge con nuestras distribuciones de CloudFront

Para nuestras distribuciones de CloudFront, configuramos cosas como:

    • Los certificados SSL para que los dominios se adjunten a la URL de CloudFront, como https://app.sendgrid.com
    • Orígenes del depósito S3
    • Grupos de origen con depósitos primarios y de réplica para conmutación por error automática
    • Comportamientos de caché

Estos comportamientos de caché, en particular, nos permiten controlar cuánto tiempo queremos que las respuestas para ciertos tipos de rutas y archivos se almacenen en caché en los servidores perimetrales de todo el mundo. Además, los comportamientos de la caché también nos brindan una forma de activar funciones de AWS Lambda en respuesta a varios eventos, como solicitudes de origen y respuestas de origen. Puede pensar en las funciones de AWS Lambda como un código específico que define y que se ejecutará en respuesta a un determinado evento.

En nuestro caso, podemos modificar los encabezados de respuesta de origen antes de que los servidores perimetrales los almacenen en caché. Nuestra función Lambda@Edge agregará encabezados de seguridad personalizados a la respuesta de origen antes de que eventualmente regrese al servidor perimetral y antes de que el usuario final reciba los archivos JavaScript, CSS y HTML con esos encabezados.

Modelamos nuestro enfoque a partir de esta publicación de blog de AWS y lo ampliamos para que sea más fácil realizar cambios en una política de seguridad de contenido específica. Puede modelar su función Lambda@Edge según la forma en que configuramos las cosas al generar listas para el script, el estilo y las fuentes de conexión. Esta función modifica efectivamente los encabezados de respuesta de origen de CloudFront y agrega cada encabezado de seguridad con ciertos valores a la respuesta antes de regresar llamando a la función de devolución de llamada proporcionada como se muestra a continuación.

¿Cómo probamos esta función de Lambda@Edge?

Antes de cambiar oficialmente cómo regresan sus activos con encabezados de seguridad, debe verificar que la función funcione después de configurar todo manualmente a través de la consola de AWS. Es crucial que sus aplicaciones web puedan cargarse y funcionar correctamente con los encabezados de seguridad agregados a sus respuestas de red. Lo último que desea escuchar es que se produzca una interrupción inesperada debido a los encabezados de seguridad, así que pruébelos minuciosamente en sus entornos de desarrollo.

También es importante saber qué escribirá exactamente en el código de Terraform más adelante para guardar esta configuración en su base de código. En caso de que no conozca Terraform, le brinda una forma de escribir y administrar su infraestructura en la nube a través del código.

Sugerencia: Eche un vistazo a los documentos de Terraform para ver si pueden ayudarlo a mantener sus configuraciones complejas sin necesidad de recordar todos los pasos que realizó en las consolas en la nube.

Cómo comenzar en la consola de AWS

Comencemos con cómo configurar las cosas manualmente a través de la consola de AWS.

  1. Primero, debe crear la función Lambda@Edge en la región "us-east-1". Al ir a la página de servicios de Lambda, haremos clic en "Crear función" y le pondremos un nombre parecido a "testSecurityHeaders1".

2. Puede usar una función existente con permisos para ejecutar la función en servidores perimetrales o puede usar una de sus plantillas de políticas de función, como "Permisos básicos de Lambda@Edge..." y llamarla "lambdaedgeroletest".

3. Después de crear su función y rol Lambda de prueba, debería ver algo como esto donde notará el botón "Agregar disparador" para la función. Aquí es donde eventualmente asociará Lambda con el comportamiento de caché de una distribución de CloudFront desencadenado en el evento de respuesta de origen.

4. A continuación, debe editar el código de función con el código de encabezados de seguridad que creamos antes y presionar "Guardar".

5. Después de guardar el código de la función, probemos si su función Lambda incluso funciona desplazándose hasta la parte superior y presionando el botón "Probar". Creará un evento de prueba llamado "samplecloudfrontresponse" utilizando la plantilla de evento "cloudfront-modify-response-header" para simular un evento de respuesta de origen de CloudFront real y ver cómo se ejecuta su función en ese caso.

Notará cosas como el objeto de encabezado "cf.response" que modificará su código de función Lambda.

6. Después de crear el evento de prueba, volverá a hacer clic en el botón "Probar" y debería ver cómo se ejecutó la función Lambda. Debería ejecutarse correctamente con registros que muestren la respuesta resultante con encabezados de seguridad agregados como este.

Genial, parece que la función Lambda agregó correctamente los encabezados de seguridad a la respuesta.

7. Volvamos al área "Diseñador" y haga clic en el botón "Agregar disparador" para que pueda asociar la función Lambda con los comportamientos de caché de su distribución de CloudFront en el evento de respuesta de origen. Asegúrese de seleccionar un activador "CloudFront" y haga clic en el botón "Implementar en Lambda@Edge".

8. A continuación, seleccione la distribución de CloudFront (en nuestro ejemplo, borramos la entrada aquí por razones de seguridad) y un comportamiento de caché para asociarlo.

Luego, elige el comportamiento de caché "*" y selecciona el evento "Respuesta de origen" para que coincida con todas las rutas de solicitud a su distribución de CloudFront y para asegurarse de que la función Lambda siempre se ejecute para todas las respuestas de origen.

Luego, marque el reconocimiento antes de hacer clic en "Implementar" para implementar oficialmente su función Lambda.

9. Después de asociar con éxito su función de Lambda con todos los comportamientos de caché de distribución de CloudFront relevantes, debería ver algo similar a esto en el área "Diseñador" del tablero de Lambda, donde puede ver los activadores de CloudFront y tener la opción de verlos o eliminarlos.

Realizar cambios en su código Lambda

Siempre que necesite realizar cambios en su código Lambda, le recomendamos:

    1. Publicar una nueva versión a través del botón desplegable "Acciones"
    2. Elimine los disparadores en la versión anterior (puede hacer clic en el menú desplegable "Calificadores" para ver todas las versiones de su Lambda)
    3. Asocie los disparadores con el número de versión más reciente que publicó recientemente

Al implementar su Lambda por primera vez o después de publicar una nueva versión de su Lambda y asociar los activadores con la versión más reciente de Lambda, es posible que no vea los encabezados de seguridad de inmediato en sus respuestas para su aplicación web. Esto se debe a cómo los servidores perimetrales en CloudFront almacenan en caché las respuestas. Dependiendo de cuánto tiempo establezca el tiempo de vida en sus comportamientos de caché, es posible que deba esperar un tiempo para ver los nuevos encabezados de seguridad a menos que realice una invalidación de caché en su distribución de CloudFront afectada.

Después de volver a implementar los cambios en la función de Lambda, a menudo la memoria caché tarda un tiempo en borrarse (según la configuración de la memoria caché de CloudFront) antes de que sus respuestas tengan los últimos ajustes en los encabezados de seguridad.

Sugerencia: para evitar actualizar mucho la página o quedarse sin saber si los cambios funcionaron, inicie una invalidación de caché de CloudFront para acelerar el proceso de limpieza del caché para que pueda ver sus encabezados de seguridad actualizados.

Vaya a la página Servicios de CloudFront, espere a que se implemente el estado de su distribución de CloudFront, lo que significa que todas las asociaciones lambda están completas e implementadas, y vaya a la pestaña "Invalidaciones". Haga clic en "Crear invalidación" y coloque "/*" como la ruta del objeto para invalidar todas las cosas en el caché y presione "Invalidar". Esto no debería tomar mucho tiempo, y una vez que se completa, la actualización de su aplicación web debería ver los últimos cambios en el encabezado de seguridad.

A medida que itera en sus encabezados de seguridad en función de lo que encuentre como violaciones o errores en su aplicación web, puede repetir este proceso:

    • Publicación de una nueva versión de la función Lambda
    • Eliminación de los activadores en la versión anterior de Lambda
    • Asociación de los disparadores en la nueva versión
    • Caché que invalida su distribución de CloudFront
    • Probando tu aplicación web
    • Repetir hasta que se sienta seguro y seguro que todo funciona como se esperaba sin páginas en blanco, solicitudes de API fallidas o errores de seguridad de la consola

Una vez que las cosas estén estables, opcionalmente puede pasar a Terraforming lo que acaba de hacer manualmente en configuraciones de código, suponiendo que tenga Terraform integrado con sus cuentas de AWS. No cubriremos cómo configurar Terraform desde el principio, pero le mostraremos fragmentos de cómo se verá el código de Terraform.

Terraformación de Lambda@Edge activada por nuestra distribución CloudFront

Después de iterar en la función Lambda@Edge para los encabezados de seguridad en la región "us-east-1", queríamos agregar esto a nuestro código base de Terraform para mantener el código y controlar las versiones en el futuro.

Para todos los comportamientos de caché que ya implementamos, tuvimos que asociar el comportamiento de caché con la función Lambda@Edge, que activó el evento de respuesta de origen.

Los siguientes pasos asumen que ya tiene la mayoría de las distribuciones de CloudFront y los depósitos de S3 configurados a través de Terraform. Nos centraremos en los principales módulos y propiedades que se relacionan con Lambda@Edge y agregaremos el disparador a los comportamientos de caché de la distribución de CloudFront. No explicaremos cómo configurar sus cubos S3 y otras configuraciones de distribución de CloudFront desde cero a través de Terraform, pero esperamos que pueda ver el nivel de esfuerzo para lograr esto por su cuenta.

Actualmente, dividimos nuestros recursos de AWS en carpetas de módulos independientes y pasamos variables a esos módulos para lograr flexibilidad en nuestra configuración. Tenemos una carpeta de apply con una subcarpeta de development y production y cada una tiene su propio archivo main.tf donde llamamos a estos módulos con ciertas variables de entrada para instanciar o modificar nuestros recursos de AWS.

Esas subcarpetas también tienen una carpeta lambdas donde guardamos nuestro código Lambda, como un archivo security_headers_lambda.js . security_headers_lambda.js tiene el mismo código que hemos estado usando en nuestra función Lambda cuando probamos manualmente, excepto que también lo estamos guardando en nuestra base de código para que podamos comprimirlo y cargarlo a través de Terraform.

1. Primero, necesitamos un módulo reutilizable para comprimir nuestro archivo Lambda antes de que se cargue y publique como otra versión de nuestra función Lambda@Edge. Esto lleva a una ruta a nuestra carpeta Lambda que contiene la función eventual Lambda de Node.js.

2. A continuación, agregamos a nuestro módulo CloudFront existente, que envuelve los depósitos S3, las políticas y los recursos de distribución de CloudFront al crear también un recurso Lambda creado a partir del archivo Lambda comprimido. Debido a que las salidas del módulo zip de Lambda pasan como variables en el módulo de CloudFront para configurar el recurso de Lambda, debemos especificar la región del proveedor de AWS como "us-east-1" y con una política de rol de trabajo como esta.

3. Dentro del módulo de CloudFront, asociamos esta función de Lambda@Edge con los comportamientos de caché de la distribución de CloudFront, como se muestra a continuación.

4. Finalmente, juntando todo en nuestro archivo main.tf de la carpeta de apply/development o apply/production , llamamos a todos estos módulos y colocamos las salidas adecuadas como variables en nuestro módulo CloudFront, como se muestra aquí.

Estos ajustes de configuración esencialmente se encargan de los pasos manuales que hicimos en la sección anterior para actualizar el código Lambda y asociar la versión más nueva con los comportamientos de caché de CloudFront y activadores para eventos de respuesta de origen. ¡Guau! No es necesario seguir o recordar los pasos de la consola de AWS siempre que apliquemos estos cambios a nuestros recursos.

¿Cómo implementamos esto de manera segura en diferentes entornos?

Cuando asociamos por primera vez nuestra función Lambda@Edge con nuestra distribución CloudFront de prueba, notamos rápidamente que nuestra aplicación web ya no se cargaba correctamente. Esto se debió principalmente a cómo implementamos nuestro encabezado Content-Security-Policy y cómo no cubría todos los recursos que cargamos en nuestra aplicación. Los otros encabezados de seguridad plantearon un riesgo menor en términos de bloquear la carga de nuestra aplicación. En el futuro, nos centraremos en implementar los encabezados de seguridad con más iteraciones en mente para ajustar el encabezado Política de seguridad de contenido.

Como se mencionó anteriormente, descubrimos cómo podemos aprovechar el encabezado Content-Security-Policy-Report-Only para minimizar el riesgo a medida que recopilamos más dominios de recursos para agregar en cada una de nuestras políticas.

En este modo de solo informe, las políticas aún se ejecutarán en el navegador y generarán mensajes de error en la consola de cualquier violación de las políticas. Sin embargo, no bloqueará directamente esos scripts y fuentes, por lo que nuestra aplicación web aún puede ejecutarse como de costumbre. Depende de nosotros continuar revisando toda la aplicación web para asegurarnos de no perder ninguna fuente importante en nuestras políticas o, de lo contrario, afectará negativamente a nuestros clientes y al equipo de soporte.

Para cada entorno, puede implementar los encabezados de seguridad Lambda de la siguiente manera:

    • Publique los cambios en su Lambda de forma manual o a través de un plan de Terraform y solicite los cambios en el entorno con otros encabezados de seguridad y el encabezado Content-Security-Policy-Report-Only primero.
    • Espere a que su estado de distribución de CloudFront se implemente por completo con el Lambda asociado con los comportamientos de caché.
    • Realice una invalidación de caché en su distribución de CloudFront si aún aparecen los encabezados de seguridad anteriores o si los cambios actuales tardan demasiado en aparecer en su navegador.
    • Visite y recorra las páginas de su aplicación web con las herramientas de desarrollador abiertas, escaneando la consola en busca de cualquier mensaje de error de la consola "Solo informar..." para mejorar su encabezado de política de seguridad de contenido.
    • Realice cambios en su código Lambda para tener en cuenta las infracciones denunciadas.
    • Repita desde el primer paso hasta que se sienta lo suficientemente seguro como para cambiar su encabezado de Content-Security-Policy-Report-Only a Content-Security-Policy, lo que significa que el entorno lo aplicará.

Mejorando nuestra puntuación de encabezados de seguridad

Después de aplicar con éxito los cambios de Terraform a nuestros entornos e invalidar los cachés de CloudFront, actualizamos las páginas en nuestra aplicación web. Mantuvimos abiertas las herramientas de desarrollo para ver los encabezados de seguridad, como HSTS, CSP y otros en nuestras respuestas de red, como los encabezados de seguridad que se muestran a continuación.

También ejecutamos nuestra aplicación web a través de un informe de escaneo de encabezados de seguridad como el de este sitio . Como resultado, fuimos testigos de grandes mejoras (¡una calificación A!) desde una calificación anterior de reprobación, y puede lograr mejoras similares después de modificar sus configuraciones de S3/CloudFront para tener encabezados de seguridad en su lugar.

Avanzando con los encabezados de seguridad

Después de configurar manualmente los encabezados de seguridad a través de la consola de AWS o de terraformar correctamente la solución y aplicar los cambios a cada uno de sus entornos, ahora tiene una gran base para seguir iterando y mejorar sus encabezados de seguridad existentes en el futuro.

Dependiendo de la evolución de su aplicación web, es posible que deba hacer que su encabezado Política de seguridad de contenido sea más específico en términos de los recursos permitidos para una seguridad más estricta. O bien, es posible que deba agregar un nuevo encabezado por completo para un propósito diferente o para llenar otro agujero de seguridad.

Con estos cambios futuros en sus encabezados de seguridad en sus funciones de Lambda@Edge, puede seguir estrategias de lanzamiento similares por entorno para asegurarse de que sus aplicaciones web estén protegidas contra ataques maliciosos en la web y sigan funcionando sin que sus usuarios noten la diferencia.

Para más artículos escritos por Alfred Lucero, vaya a la página del autor de su blog: https://sendgrid.com/blog/author/alfred/