Добавьте заголовки безопасности с помощью Lambda@Edge и Terraform в AWS S3/CloudFront

Опубликовано: 2021-04-24

Когда вы входите на https://app.sendgrid.com , чтобы проверить данные своей учетной записи, или посещаете https://mc.sendgrid.com для маркетинговых кампаний, вы посещаете наши клиентские веб-приложения, размещенные в корзинах AWS S3 с Дистрибутивы CloudFront поверх них.

Наши веб-приложения в основном состоят из уменьшенных и разделенных на части кода файлов JavaScript и CSS, файлов HTML и файлов изображений, загружаемых в корзины S3 в качестве кэшей CloudFront и предоставляющих их нашим пользователям. Каждая из сред нашего веб-приложения, начиная от тестовой, промежуточной и рабочей, имеет отдельную корзину S3 и дистрибутив CloudFront.

Эта инфраструктура AWS S3 и CloudFront хорошо работает для наших масштабируемых веб-приложений при размещении файлов в сети доставки контента, но в наших первоначальных конфигурациях не хватало более надежной защиты в виде заголовков безопасности.

Добавление этих заголовков безопасности защитит пользователей от атак, таких как межсайтовый скриптинг, прослушивание MIME, кликджекинг, внедрение кода и атаки «человек посередине», связанные с небезопасными протоколами. Если оставить их без внимания, это будет иметь серьезные последствия для данных наших клиентов и для доверия нашей компании к способности обеспечить безопасный опыт работы в Интернете.

Прежде чем мы исследовали, как добавить эти заголовки, мы сначала сделали шаг назад, чтобы увидеть, где мы были. После запуска URL-адреса нашего веб-приложения через веб- сайт сканирования заголовков безопасности мы, что неудивительно, получили неудовлетворительную оценку, но увидели полезный список заголовков для изучения, как показано ниже.

Как видите, было много возможностей для улучшения. Мы исследовали, как настроить наши ресурсы AWS S3 и CloudFront, чтобы они отвечали заголовками безопасности, чтобы снизить упомянутые риски и уязвимости.

На высоком уровне мы можем добиться этого, создав функцию Lambda@Edge, которая изменяет заголовки ответа источника, чтобы добавить нужные заголовки безопасности до того, как файлы веб-приложения вернутся в браузер пользователя.

Стратегия заключается в том, чтобы сначала протестировать подключение вручную через консоль AWS. Затем мы поместим эти конфигурации в Terraform, чтобы сохранить эту часть инфраструктуры в коде для дальнейшего использования и совместного использования другими командами и приложениями.

Какие заголовки безопасности мы хотели бы добавить?

В рамках рекомендаций нашей группы по безопасности продуктов нам было поручено добавить заголовки безопасности, такие как «Strict-Transport-Security» и «X-Frame-Options». Мы рекомендуем вам также ознакомиться с такими ресурсами, как Cheatsheet MDN Web Security, чтобы освоиться. Вот краткий обзор заголовков безопасности, которые вы можете применить к своим веб-приложениям.

Строгая транспортная безопасность (HSTS)

Это делается для того, чтобы предоставить браузеру подсказки для доступа к вашему веб-приложению через HTTPS, а не через HTTP.

Политика безопасности контента (CSP)

Это делается для установки явных списков разрешенных ресурсов, которые вы загружаете или к которым подключаетесь в своем веб-приложении, таких как сценарии, изображения, стили, шрифты, сетевые запросы и фреймы. Это была самая сложная для нас настройка, поскольку у нас были сторонние скрипты, изображения, стили и конечные точки API для явной записи в этих политиках.

Совет. Используйте заголовок Content-Security-Policy-Report-Only , чтобы упростить тестирование в определенных средах. Если определенные ресурсы нарушали политики, мы наблюдали полезные выходные данные консоли о ресурсах, которые нам нужно было разрешить в наших политиках.

Если вы хотите избежать забавного инцидента с пустыми экранами и невозможностью загрузки веб-приложений, мы настоятельно рекомендуем вам сначала поэкспериментировать со своими политиками в режиме только для отчетов и провести тщательное тестирование, прежде чем чувствовать себя достаточно уверенно, чтобы развертывать эти политики безопасности в рабочей среде.

X-Content-Type-Options

Это необходимо для поддержки и загрузки ресурсов с правильными типами MIME на вашей веб-странице.

X-Frame-Параметры

Это необходимо для предоставления правил того, как ваше веб-приложение потенциально загружается в iframe.

X-XSS-защита

Это останавливает загрузку страниц, если в некоторых браузерах обнаруживается атака с использованием межсайтовых сценариев.

Реферальная политика

Это управляет тем, как заголовок «Referrer» с информацией об источнике запроса передается при переходе по ссылкам на внешние сайты или ресурсы.

Имея в виду эти заголовки безопасности, давайте вернемся к тому, как мы сегодня настроили наши дистрибутивы CloudFront и как функции Lambda@Edge помогут нам достичь нашей цели.

Использование Lambda@Edge с нашими дистрибутивами CloudFront

Для наших дистрибутивов CloudFront мы настраиваем такие вещи, как:

    • SSL-сертификаты для доменов, которые нужно прикрепить поверх URL-адреса CloudFront, например https://app.sendgrid.com .
    • Происхождение корзины S3
    • Исходные группы с основными корзинами и корзинами-репликами для автоматического перехода на другой ресурс
    • Поведение кэша

Такое поведение кэша, в частности, позволяет нам контролировать, как долго мы хотим, чтобы ответы для определенных типов путей и файлов кэшировались на пограничных серверах по всему миру. Кроме того, поведение кеша также позволяет нам запускать функции AWS Lambda в ответ на различные события, такие как запросы источника и ответы источника. Вы можете думать о функциях AWS Lambda как о конкретном определяемом вами коде, который будет выполняться в ответ на определенное событие.

В нашем случае мы можем изменить заголовки ответа источника до того, как он будет кэширован пограничными серверами. Наша функция Lambda@Edge добавит настраиваемые заголовки безопасности к исходному ответу, прежде чем он в конечном итоге вернется обратно на пограничный сервер и до того, как конечный пользователь получит файлы JavaScript, CSS и HTML с этими заголовками.

Мы смоделировали наш подход на основе этого поста в блоге AWS и расширили его, чтобы упростить внесение изменений в конкретную политику безопасности контента. Вы можете смоделировать свою функцию Lambda@Edge так же, как мы настроили ее при создании списков для сценария, стиля и источников подключения. Эта функция эффективно изменяет заголовки ответа источника CloudFront и добавляет к ответу каждый заголовок безопасности с определенными значениями перед возвратом путем вызова предоставленной функции обратного вызова, как показано ниже.

Как мы тестируем эту функцию Lambda@Edge?

Прежде чем официально изменить способ возврата ваших активов с помощью заголовков безопасности, вы должны убедиться, что функция работает после того, как вы настроите все вручную через консоль AWS. Крайне важно, чтобы ваши веб-приложения могли загружаться и функционировать должным образом с добавлением заголовков безопасности к вашим сетевым ответам. Последнее, что вы хотите услышать, это неожиданный сбой, происходящий из-за заголовков безопасности, поэтому тщательно протестируйте их в своей среде разработки.

Также важно знать, что именно вы будете писать в коде Terraform позже, чтобы сохранить эту конфигурацию в своей кодовой базе. Если вы не знаете о Terraform, он предоставляет вам возможность писать и управлять вашей облачной инфраструктурой с помощью кода.

Совет. Ознакомьтесь с документацией Terraform , чтобы узнать, поможет ли она поддерживать сложные конфигурации без необходимости запоминать все шаги, которые вы выполняли в облачных консолях.

Как начать работу в консоли AWS

Давайте начнем с того, как настроить все вручную через консоль AWS.

  1. Во-первых, вам нужно создать функцию Lambda@Edge в регионе «us-east-1». Перейдя на страницу сервисов Lambda, мы нажмем «Создать функцию» и назовем ее что-то вроде «testSecurityHeaders1».

2. Вы можете использовать существующую роль с разрешениями для запуска функции на пограничных серверах или использовать один из их шаблонов политик ролей, например «Основные разрешения Lambda@Edge…», и назвать его «lambdaedgeroletest».

3. После создания тестовой функции и роли Lambda вы должны увидеть что-то вроде этого, где вы увидите кнопку «Добавить триггер» для функции. Именно здесь вы в конечном итоге свяжете Lambda с поведением кэша раздачи CloudFront, активируемым в ответном событии источника.

4. Затем вам нужно отредактировать код функции с кодом заголовков безопасности, который мы создали ранее, и нажать «Сохранить».

5. После сохранения кода функции давайте проверим, работает ли ваша функция Lambda, прокрутив вверх и нажав кнопку «Проверить». Вы создадите тестовое событие с именем «samplecloudfrontresponse», используя шаблон события «cloudfront-modify-response-header», чтобы имитировать фактическое событие ответа источника CloudFront и посмотреть, как ваша функция работает с ним.

Вы заметите такие вещи, как объект заголовков «cf.response», который будет изменен кодом вашей функции Lambda.

6. После создания тестового события вы снова нажмете кнопку «Тест» и увидите, как с ним работает функция Lambda. Он должен успешно работать с журналами, отображающими результирующий ответ с добавленными заголовками безопасности, подобными этому.

Отлично, функция Lambda выглядит так, как будто она правильно добавила заголовки безопасности к ответу!

7. Давайте вернемся в область «Конструктор» и нажмем кнопку «Добавить триггер», чтобы вы могли связать функцию Lambda с поведением кэша дистрибутива CloudFront в событии ответа источника. Обязательно выберите триггер «CloudFront» и нажмите кнопку «Развернуть в Lambda@Edge».

8. Затем выберите дистрибутив CloudFront (в нашем примере мы очистили входные данные здесь из соображений безопасности) и связанное с ним поведение кэша.

Затем вы выбираете поведение кэша «*» и выбираете событие «Ответ источника», чтобы оно соответствовало всем путям запросов к вашей раздаче CloudFront и чтобы функция Lambda всегда выполнялась для всех ответов источника.

Затем вы отмечаете подтверждение, прежде чем нажать «Развернуть», чтобы официально развернуть свою функцию Lambda.

9. После успешного связывания вашей функции Lambda со всеми соответствующими режимами кэширования вашей раздачи CloudFront вы должны увидеть что-то похожее на это в области «Конструктор» панели мониторинга Lambda, где вы можете увидеть триггеры CloudFront и иметь возможность просмотреть или удалить их.

Внесение изменений в ваш лямбда-код

Всякий раз, когда вам может понадобиться внести изменения в код Lambda, мы рекомендуем:

    1. Опубликуйте новую версию через выпадающее меню кнопки «Действия».
    2. Удалите триггеры в более старой версии (вы можете щелкнуть раскрывающийся список «Квалификаторы», чтобы увидеть все версии вашей Lambda)
    3. Свяжите триггеры с последним номером версии, который вы недавно опубликовали.

При первом развертывании Lambda или после публикации новой версии Lambda и связывания триггеров с более новой версией Lambda вы можете не сразу увидеть заголовки безопасности в своих ответах для своего веб-приложения. Это связано с тем, как пограничные серверы в CloudFront кэшируют ответы. В зависимости от того, как долго вы задали время жизни в своем поведении кэша, вам, возможно, придется подождать некоторое время, чтобы увидеть новые заголовки безопасности, если только вы не аннулируете кэш в своем уязвимом дистрибутиве CloudFront.

После повторного развертывания изменений в функции Lambda часто требуется время для очистки кэша (в зависимости от настроек кэша CloudFront), прежде чем в ваших ответах появятся последние настройки заголовков безопасности.

Совет. Чтобы не обновлять страницу слишком часто и не сидеть сложа руки, не зная, сработали ли ваши изменения, запустите аннулирование кеша CloudFront, чтобы ускорить процесс очистки кеша и увидеть обновленные заголовки безопасности.

Перейдите на страницу CloudFront Services, подождите, пока статус вашего дистрибутива CloudFront будет развернут, что означает, что все лямбда-ассоциации завершены и развернуты, и перейдите на вкладку «Недействительные». Нажмите «Создать инвалидацию» и введите «/*» в качестве пути к объекту, чтобы сделать недействительными все вещи в кеше, и нажмите «Недействительно». Это не должно занять слишком много времени, и после обновления веб-приложения вы увидите последние изменения в заголовке безопасности.

По мере того, как вы повторяете свои заголовки безопасности на основе того, что вы считаете нарушениями или ошибками в своем веб-приложении, вы можете повторить этот процесс:

    • Публикация новой версии функции Lambda
    • Удаление триггеров на старой версии Lambda
    • Связывание триггеров в новой версии
    • Кэш делает недействительным ваш дистрибутив CloudFront
    • Тестирование вашего веб-приложения
    • Повторяйте, пока не почувствуете себя уверенно и безопасно, все работает как положено, без пустых страниц, неудачных запросов API или ошибок безопасности консоли.

Как только все станет стабильным, вы можете при желании перейти к Terraforming того, что вы только что сделали вручную, в конфигурации кода, при условии, что Terraform интегрирован с вашими учетными записями AWS. Мы не будем рассказывать, как настроить Terraform с самого начала, но мы покажем вам фрагменты того, как будет выглядеть код Terraform.

Терраформирование Lambda@Edge, вызванное нашим дистрибутивом CloudFront

После итерации функции Lambda@Edge для заголовков безопасности в регионе «us-east-1» мы хотели добавить ее в нашу кодовую базу Terraform для удобства сопровождения кода и контроля версий в будущем.

Для всех вариантов поведения кэша, которые мы уже реализовали, нам пришлось связать поведение кэша с функцией Lambda@Edge, которую запускало событие ответа источника.

В следующих шагах предполагается, что у вас уже настроено большинство дистрибутивов CloudFront и корзин S3 с помощью Terraform. Мы сосредоточимся на основных модулях и свойствах, связанных с Lambda@Edge, и добавим триггер в поведение кэша дистрибутива CloudFront. Мы не будем подробно описывать, как настроить корзины S3 и другие параметры дистрибутива CloudFront с нуля с помощью Terraform, но мы надеемся, что вы сможете увидеть уровень усилий, необходимых для выполнения этого самостоятельно.

В настоящее время мы разбиваем наши ресурсы AWS на отдельные папки модулей и передаем переменные в эти модули для гибкости нашей конфигурации. У нас есть папка apply с подпапкой development и production , и у каждого есть свой файл main.tf , где мы вызываем эти модули с определенными входными переменными для создания или изменения наших ресурсов AWS.

В каждой из этих подпапок также есть папка lambdas , в которой мы храним наш код Lambda, такой как файл security_headers_lambda.js . В security_headers_lambda.js тот же самый код, который мы использовали в нашей функции Lambda при ручном тестировании, за исключением того, что мы также сохраняем его в нашей кодовой базе, чтобы заархивировать и загрузить через Terraform.

1. Во-первых, нам нужен многоразовый модуль, чтобы заархивировать наш файл Lambda, прежде чем он будет загружен и опубликован как еще одна версия нашей функции Lambda@Edge. Это указывает путь к нашей папке Lambda, содержащей возможную функцию Node.js Lambda.

2. Затем мы добавляем в наш существующий модуль CloudFront, который объединяет корзины S3, политики и ресурсы распространения CloudFront, также создавая ресурс Lambda, созданный из заархивированного файла Lambda. Поскольку выходные данные zip-модуля Lambda передаются как переменные в модуль CloudFront для настройки ресурса Lambda, нам нужно указать регион поставщика AWS как «us-east-1» и с такой политикой рабочей роли.

3. Затем в модуле CloudFront мы связываем эту функцию Lambda@Edge с поведением кэша дистрибутива CloudFront, как показано ниже.

4. Наконец, поместив все это вместе в файл main.tf папки apply/development или apply/production , мы вызываем все эти модули и помещаем соответствующие выходные данные в качестве переменных в наш модуль CloudFront, как показано здесь.

Эти настройки конфигурации, по сути, выполняют действия, которые мы выполнили вручную в предыдущем разделе, чтобы обновить код Lambda и связать более новую версию с поведением кэша CloudFront и триггерами для событий ответа источника. Ууууу! Нет необходимости проходить или запоминать шаги консоли AWS, пока мы применяем эти изменения к нашим ресурсам.

Как мы можем безопасно развернуть это в разных средах?

Когда мы впервые связали нашу функцию Lambda@Edge с нашим тестовым дистрибутивом CloudFront, мы быстро заметили, что наше веб-приложение перестало загружаться правильно. В основном это было связано с тем, как мы реализовали наш заголовок Content-Security-Policy и тем, что он не охватывал все ресурсы, которые мы загружали в наше приложение. Другие заголовки безопасности представляли меньший риск с точки зрения блокировки загрузки нашего приложения. В будущем мы сосредоточимся на развертывании заголовков безопасности с учетом дальнейших итераций для точной настройки заголовка Content-Security-Policy.

Как упоминалось ранее, мы обнаружили, как мы можем использовать заголовок Content-Security-Policy-Report-Only вместо того, чтобы минимизировать риск, поскольку мы собираем больше доменов ресурсов для добавления в каждую из наших политик.

В этом режиме только для отчетов политики по-прежнему будут работать в браузере и выводить на консоль сообщения об ошибках любых нарушений политик. Однако он не будет напрямую блокировать эти сценарии и источники, поэтому наше веб-приложение может работать в обычном режиме. Мы должны продолжать просматривать все веб-приложение, чтобы убедиться, что мы не пропустим ни одного важного источника в наших политиках, иначе это негативно повлияет на наших клиентов и службу поддержки.

Для каждой среды вы можете развернуть заголовки безопасности Lambda следующим образом:

    • Опубликуйте изменения в своей Lambda либо вручную, либо через план Terraform и сначала примените изменения к среде с другими заголовками безопасности и заголовком Content-Security-Policy-Report-Only.
    • Дождитесь полного развертывания статуса дистрибутива CloudFront с лямбдой, связанной с поведением кэша.
    • Выполните аннулирование кеша в своей раздаче CloudFront, если предыдущие заголовки безопасности все еще отображаются, или ваши текущие изменения отображаются в вашем браузере слишком долго.
    • Посетите и просмотрите страницы своего веб-приложения с открытыми инструментами разработчика, просканировав консоль на наличие сообщений об ошибках консоли «Только отчет…», чтобы улучшить заголовок Content-Security-Policy.
    • Внесите изменения в свой лямбда-код, чтобы учесть эти сообщения о нарушениях.
    • Повторяйте с первого шага, пока не почувствуете себя достаточно уверенно, чтобы изменить свой заголовок с Content-Security-Policy-Report-Only на Content-Security-Policy, что означает, что среда будет применять его.

Улучшение нашей оценки заголовков безопасности

После успешного применения изменений Terraform к нашим средам и аннулирования кешей CloudFront мы обновили страницы в нашем веб-приложении. Мы оставили инструменты разработчика открытыми, чтобы видеть заголовки безопасности, такие как HSTS, CSP и другие, в наших сетевых ответах, таких как заголовки безопасности, показанные ниже.

Мы также запустили наше веб-приложение с помощью отчета о сканировании заголовков безопасности, такого как тот, что находится на этом сайте . В результате мы стали свидетелями значительных улучшений (рейтинг A!) по сравнению с ранее неудовлетворительной оценкой, и вы можете добиться аналогичных улучшений, изменив настройки S3/CloudFront, чтобы установить заголовки безопасности.

Двигаемся вперед с заголовками безопасности

После ручной настройки заголовков безопасности с помощью консоли AWS или успешного терраформирования решения и применения изменений к каждой из ваших сред теперь у вас есть отличная основа для дальнейшей итерации и улучшения существующих заголовков безопасности в будущем.

В зависимости от эволюции вашего веб-приложения вам, возможно, придется сделать заголовок Content-Security-Policy более конкретным с точки зрения ресурсов, разрешенных для более строгой безопасности. Или вам может понадобиться добавить новый заголовок для отдельной цели или заполнить другую дыру в безопасности.

С этими будущими изменениями в ваших заголовках безопасности в функциях Lambda@Edge вы можете следовать аналогичным стратегиям выпуска для каждой среды, чтобы быть уверенными, что ваши веб-приложения защищены от вредоносных атак в Интернете и продолжают работать, а ваши пользователи даже не заметят разницы.

Для получения дополнительных статей, написанных Альфредом Лусеро, перейдите на страницу автора его блога: https://sendgrid.com/blog/author/alfred/