Как добавить аутентификацию OAuth2 в статическую корзину S3 с помощью Okta
4 апреля 2022 г.Наша команда недавно внедрила внутренний статический веб-сайт, который позволяет сотрудникам загружать технические отчеты. Поскольку мы активно пользуемся AWS, мы, естественно, решили разместить его на AWS S3, который предоставляет специальную функцию для создания статических веб-сайтов ([хостинг статических веб-сайтов S3] (https://docs.aws.amazon.com/AmazonS3/latest /userguide/WebsiteHosting.html)).
Однако очень быстро мы столкнулись с проблемой: AWS S3 не предоставляет никакого встроенного процесса аутентификации/авторизации. Поскольку это был веб-сайт только для внутреннего пользования, нам требовался какой-то механизм авторизации, чтобы предотвратить доступ неавторизованных пользователей к нашему веб-сайту и отчетам.
Нам нужно было найти решение для защиты нашего статического веб-сайта на AWS S3.
Обнаружение решения с помощью CloudFront и Lambda@Edge
Мы используем [Okta] (https://www.okta.com/) для всех идентификаций и управления пользователями, поэтому любое решение, которое мы нашли, должно было подключаться к Okta.
Okta имеет несколько потоков аутентификации/авторизации, каждый из которых требует, чтобы приложение выполняло внутреннюю проверку, например проверку того, что ответ/токен, возвращаемый Okta, является законным. Поэтому нам нужно было найти способ выполнять эти проверки/действия на статическом веб-сайте, который использует серверную часть, которую мы не контролируем.
Именно тогда мы узнали об AWS Lambda@Edge, который позволяет запускать функции Lambda на разных этапах запроса и ответа на CloudFront и из него:
Мы можем запустить лямбда-функцию на четырех разных этапах:
- Когда запрос поступает в CloudFront (
viewer-request
)
- Когда запрос отправляется к источнику (
origin-request
)
- Когда ответ возвращается из источника (
origin-response
)
- Когда ответ возвращается из CloudFront (
отклик зрителя
)
Мы увидели решение нашей первоначальной проблемы: запустить Lambda на этапе viewer-request
, который будет проверять, авторизован ли пользователь, при двух условиях:
- Если пользователь авторизован, пусть запрос продолжится и вернет контент с ограниченным доступом
- Если пользователь не авторизован, отправьте HTTP-ответ, чтобы перенаправить его на страницу входа
Реализация функции Lambda@Edge
Здесь мы рассмотрим ключевые элементы и основные проблемы, с которыми мы столкнулись. Полный код доступен [здесь] (https://github.com/GuiTeK/aws-s3-oauth2-okta). Не стесняйтесь использовать его в своем проекте!
Ограничения и предостережения Lambda@Edge
При разработке решения мы столкнулись с рядом ограничений и предостережений Lambda@Edge.
1 – Переменные среды
Функции Lambda@Edge не могут использовать переменные среды. Это означало, что нам нужно было найти другой способ передачи данных в нашу функцию. Мы выбрали параметры SSM и шаблоны имен параметров в коде Node.js (мы используем Terraform для рендеринга шаблона при развертывании лямбда-функции).
2 – Ограничение размера пакета Lambda
Для событий просмотра (напоминание: мы используем событие viewer-request
) размер пакета Lambda может составлять не более 1 МБ. Один МБ довольно мал, учитывая, что он включает в себя все зависимости (кроме, конечно, среды выполнения/стандартной библиотеки) вашей лямбда-функции.
Вот почему нам пришлось переписать нашу Lambda на Node.js вместо оригинального Python, потому что пакет Python с его зависимостями превышал ограничение в 1 МБ.
3 – область лямбда
Функции Lambda@Edge можно создавать только в регионе us-east-1
. Это не большая проблема, но это означает, что вам нужно:
- Предоставьте свои ресурсы AWS в этом регионе, чтобы упростить задачу.
- В Terraform вам потребуется отдельный «провайдер» AWS для доступа к корзине, которую вы хотите защитить, если она не находится в «us-east-1».
4 – Разрешение роли Lambda
Роль выполнения IAM, связанная с функциями Lambda@Edge, должна разрешать основной сервис edgelambda.amazonaws.com
в дополнение к обычному lambda.amazonaws.com
. См. AWS — настройка разрешений и ролей IAM для Lambda@Edge.
Механизм авторизации с Okta
Как только мы справились с вышеуказанными ограничениями и предостережениями, мы сосредоточились на авторизации/авторизации.
Okta предлагает несколько способов аутентификации и авторизации пользователей. Мы решили использовать OAuth2, стандартный протокол авторизации.
Примечание. Okta реализует стандарт OpenID Connect (OIDC), который добавляет тонкий слой аутентификации поверх OAuth2 (это цель токена ID, упомянутого ниже). Наше решение также будет работать с чистым OAuth2 с минимальными изменениями (удаление использования токена ID в коде).
Сам OAuth2 предлагает несколько потоков авторизации в зависимости от типа приложения, которое его использует. В нашем случае нам нужен поток кода авторизации.
Вот полная схема потока кода авторизации, взятая с developer.okta.com, которая показывает, как это работает. :
Подводя итог потоку:
- Наша лямбда-функция перенаправляет пользователя на Okta, где ему будет предложено войти в систему.
- Okta перенаправляет пользователя на наш сайт/лямбда-функцию с помощью кода
- Наша лямбда-функция проверяет легитимность кода и обменивает его на доступ и ID токены, отправляя запрос в Okta
- В зависимости от результата, возвращаемого Okta, мы:
- Разрешить или запретить доступ к ограниченному контенту
- Если доступ разрешен, сохраните токены доступа и идентификатора в файле cookie, чтобы избежать повторной авторизации пользователя на каждой странице.
Использование JSON Web Tokens для хранения результата авторизации
Пока у нас есть работающий процесс авторизации; однако нам необходимо проверять токен доступа/идентификатора при каждом запросе (злоумышленник может подделать недопустимый файл cookie/токены). Проверка токенов означает отправку запроса в Okta и ожидание ответа на каждой странице, которую посещает пользователь, что значительно замедляет время загрузки и явно неоптимально.
Примечание. Хотя локальная проверка токена Okta теоретически возможна, на момент написания этой статьи SDK, предоставленный Okta использует кэш LRU (в памяти) при извлечении ключей, используемых для проверки токенов. Поскольку мы используем AWS Lambda, а память/состояние программы не сохраняются между вызовами, SDK для нас бесполезен: он по-прежнему будет отправлять один HTTP-запрос к Okta для каждого пользовательского запроса, чтобы получить JWK (JSON Web Ключи). Хуже того, существует ограничение в 10 запросов JWK в минуту, из-за чего наше решение перестанет работать, если будет более 10 запросов в минуту.
Мы решили использовать веб-токены JSON, чтобы обойти эту проблему. Начальный процесс авторизации такой же, за исключением того, что вместо сохранения токенов доступа/идентификатора в файле cookie мы создаем JWT, содержащий эти токены, а затем сохраняем JWT в файле cookie.
Поскольку JWT криптографически подписан:
- Злоумышленник не может его подделать (для подписи ему потребуется закрытый ключ)
- Этап проверки, требуемый для каждого запроса, выполняется быстро: мы заменили длинный HTTP-запрос с большими затратами ввода-вывода на быструю криптографическую проверку.
Примечание об истечении срока действия и продлении JWT
JWT имеет предварительно определенный срок действия, который должен быть достаточно коротким, чтобы избежать наличия действительного JWT, содержащего просроченные или отозванные токены доступа/идентификатора. Другим вариантом может быть регулярная проверка токенов доступа/идентификатора и отзыв связанного JWT, если это необходимо, но тогда нам понадобится механизм отзыва, который усложнит ситуацию.
Наконец, как было сказано выше, токены, предоставляемые Okta, имеют срок действия. Можно прозрачно обновлять их с помощью токена обновления (чтобы пользователю не приходилось повторно входить в систему по истечении срока действия токенов), но мы этого не реализовали.
Заключение
Хотя добавление аутентификации OAuth2 в статическую корзину S3 с помощью Okta (или любого другого провайдера OAuth2) возможно интегрированным с AWS и безопасным образом, это, безусловно, непросто.
Требуется написать промежуточное ПО между AWS и провайдером OAuth2 (в нашем случае Okta) с использованием Lambda@Edge.
Пришлось самим делать следующее:
- Подтвердить аутентификацию пользователя
- Помните об аутентификации пользователя
- Обновите аутентификацию пользователя (не реализовано в нашем решении)
- Отменить аутентификацию пользователя (TTL реализован, но отзыв до окончания TTL - нет)
Наконец, необходимо создать кучу ресурсов AWS, чтобы склеить все вместе и заставить это работать.
Все это стоит затраченных усилий, потому что это работает, и наш сайт стал более безопасным.
Вы можете найти код Lambda@Edge, а также инфраструктуру (Terraform) здесь:
https://github.com/GuiTeK/aws-s3-oauth2-okta
Автор: Гийом Трюшо, инженер по надежности сайта в Algolia, GitHub
Оригинал