Некоторое время назад я начал писать прокси с поддержкой идентификации (IAP) для защиты двоичного файла с помощью аутентификации. Однако то, что начиналось как минимальный уровень аутентификации, расширилось за счет функций. Я пришел к выводу, что обратный прокси-сервер - отличный уровень для решения множества сквозных задач, таких как аутентификация, хотя бы один раз доставка и адаптация. Кроме того, я обнаружил, что OpenResty обеспечивает потрясающую производительность и гибкость, И он почти идеально подходит для бессерверной парадигмы.

Конкретно я работал над расширением IAP для приема и изменения сигналов от Slack и Zapier, туннелирования их через журнал предварительной записи (WAL) и проверки их подлинности до того, как они попадут в двоичный файл нашего приложения. Оказывается, такая интеграция на уровне прокси дает огромные технические преимущества.

Первая победа для прокси - это универсальный адаптер. Часто требуется изменить форму JSON, которыми обмениваются независимо разработанные службы. Учитывая полезность обертывания сервисов общим слоем аутентификации, имеет смысл, что это также удобный момент для сопоставления доменов. С OpenResty вы можете сделать это в высокопроизводительном двоичном файле.

Вторая победа заключалась в использовании прокси для минимизации задержки ответа. Slack требует, чтобы боты ответили в течение 3 секунд. Если восходящий поток является бессерверным процессом JVM, вы можете легко истечь тайм-аут, когда восходящий поток холодный. Мы решили эту проблему на уровне прокси, буферизовав входящие запросы в управляемую очередь, что-то вроде журнала предзаписи (WAL). Это означало, что мы могли снизить задержку, отвечая на веб-перехватчик Slack, как только очередь подтверждала запись. Поскольку OpenResty - это c + lua, запускается так быстро, что мы делаем все, что в наших силах, в бессерверной среде.

С WAL мы получаем семантику доставки хотя бы один раз. Размещение WAL на уровне прокси может решить массу проблем с надежностью восходящего потока. Это означает, что, пока восходящий поток идемпотентен, вам не нужна логика повторных попыток восходящего потока. Это упрощает разработку приложений и расширяет выбор стека в восходящем направлении. Конкретно для нас это означало, что двоичный файл JVM с медленным запуском не нужно переписывать для развертывания на бессерверной основе.

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

В целом мы обнаружили, что OpenResty - почти идеальная технология для Serverless. Это дает вам производительность C (задержка отклика до 5 мс), быстрое время запуска (400 мс холодный запуск в Cloud Run) и готовность Ngnix к производству, и все это дает вам гибкость для настройки и добавления функций к границе вашей инфраструктуры с помощью Скрипты Lua.

Стоит отметить, что Cloud Run масштабируется до нуля (в отличие от Fargate) и поддерживает параллельные операции (в отличие от Lambda и Google Cloud Functions). OpenResty + Cloud Run позволит вам обслуживать тонны одновременного трафика на одном экземпляре, и поэтому я ожидаю, что это будет наиболее рентабельным из возможных вариантов. Хотя его холодный запуск выше, чем, скажем, Лямбда (мы получаем 400 мс против 200 мс), поскольку он требует меньше событий масштабирования, я ожидаю, что инциденты холодного запуска будут менее частыми для большинства развертываний.

Благодаря тому, что прокси обрабатывает больше вариантов использования (например, логика повторных попыток), затраты переносятся из двоичных файлов приложения в самую удобную часть инфраструктуры. Чтобы воспользоваться всеми этими преимуществами, вам не нужен кластер Kubernetes, но вы можете развернуть его в кластере, если хотите. Нам удалось объединить все наши функции в единую небольшую бессерверную службу, развернутую Terraform по лицензии MIT.

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

Локальная разработка с Terraform и Docker-compose

Медленность развертываний Cloud Run препятствовала разработке сложных функций прокси, поэтому это первая проблема, которая решила проблему локальной разработки. Поскольку Cloud Run в конечном итоге развертывает файлы докеров, мы использовали docker-compose, чтобы запустить наш двоичный файл вместе с эмулятором метаданных GCE. Наш файл докеров создается из шаблонов Terraform, но вы можете попросить Terraform создать этот локальный файл, без развертывания всего проекта, с флагом -target. Таким образом, мы можем создать полуприличный цикл разработки, упорядочивая генерацию артефактов терраформ и docker-compose в цикле, и не нужно переписывать рецепт Terraform для поддержки локальной разработки!

В приведенном выше сценарии оболочки, когда вы нажимаете CTRL + C в оболочке, двоичный файл обновится и перезапустится. Сложность - выйти из этого цикла! Если вы вызовете его с помощью «/ bin / bash test / dev.sh», он будет называться bash, поэтому вы можете выйти с помощью «killall bash». Redirect_uri Oauth 2.0 не будет работать с localhost, поэтому вам нужно будет скопировать токены prod с / login? Token = true из развертывания prod.

Добавление журнала упреждающей записи с помощью Pub / Sub

Чтобы иметь возможность быстро и уверенно отвечать на входящие запросы, к прокси-серверу было добавлено внутреннее расположение общего назначения /wal/.... Предполагалось, что любой запрос, направленный на /wal/<PATH>, будет адресован UPSTREAM/<PATH>, но будет перемещаться через тему Pub / Sub и подписку. Это выгружает постоянное хранилище буфера в выделенную службу с большой задержкой и гарантиями надежности.

Каждое сообщение WAL, по сути, инкапсулирует HTTP-запрос. Итак, заголовки, uri, method и body были помещены в конверт и отправлены в PubSub. Тело запроса было сопоставлено с полем данных Pub ​​/ Sub в кодировке base64, и мы использовали атрибуты Pub / Sub для хранения остальных.

Вызов Pub / Sub - это простой POST-запрос к теме, предоставленной Terraform.

Предусмотрена подписка Pub / Sub, которая отправит конверты обратно на прокси-сервер в расположение /wal-playback. Указав oidc_token, Pub / Sub добавит токен идентификатора, который можно проверить на прокси-сервере.

В конфигурации OpenResty мы открываем /wal-playback для Интернета, но мы проверяем входящий токен перед распаковкой конверта и отправкой в ​​восходящем направлении.

В нашем случае наш апстрим тоже был размещен в Cloud Run. Если ответом восходящего потока был код состояния 429 (слишком много запросов), это означает, что контейнер увеличивается и его следует повторить. Точно так же код состояния 500 означает, что восходящий поток прерван и запрос следует повторить. Для этих кодов ответа прокси-сервер возвращает статус 500 в Pub / Sub, который запускает его поведение повторной попытки, что приводит к семантике доставки хотя бы один раз.

В нашем развертывании интеграции Zapier и Slack использовали прокси /wal/.

Интеграция Slack

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

Slack отправляет исходящие x-www-form-urlencoded веб-перехватчиков. Конечно, наш апстрим использует JSON, но конвертировать его с помощью пакета resty-reqargs несложно.

Поскольку это общедоступная конечная точка, нам необходимо обеспечить подлинность запроса. Slack использует общий симметричный ключ подписи. Поскольку нам не нужны секреты рядом с Terraform. Копируем вручную ключ в Google Secret Manager.

Тогда нам нужно только сохранить идентификатор ресурса ключа в Terraform. Кстати, Секретный менеджер отличный! Вы можете ссылаться на последнюю версию, чтобы вы могли менять секреты, не беспокоя Terraform.

В конфигурации OpenResty мы получаем секрет с помощью аутентифицированного GET и декодирования base64. Мы храним секрет в глобальной переменной для использования во всех запросах.

Документация Slack довольно хорошо объясняет, как проверить запрос. При использовании пакета resty.hmac было всего несколько строк lua:

Конечно, настоящая трудность со Slack заключается в требовании тайм-аута в 3 секунды, поэтому входящие команды slack перенаправлялись в WAL для быстрого ответа с семантикой доставки хотя бы один раз.

Интеграция Zapier

Zapier - еще одна отличная интеграция с выгодной ценой. Если у вас есть Identity Aware Proxy, легко создать внутреннее приложение, которое может вызывать ваши API.

После создания приложения Zapier вам необходимо добавить Zapier в качестве авторизованного URL-адреса перенаправления.

Чтобы авторизация работала бессрочно с Google Auth, вам необходимо добавить параметры в запрос токена, чтобы включить автономный доступ.

Вам нужен только адрес электронной почты:

А чтобы конечная точка токена обновления работала правильно, вам нужно добавить идентификатор клиента и секрет:

Чтобы отправить сигнал от Zapier нашему внутреннему программному обеспечению, мы создали действие с именем signal, у которого был ключ имени плюс строка, словарь строк переменных. Это похоже на минимальную, но гибкую схему.

Его очень хорошо Zapier работает с Oauth 2.0, и это помогло проверить правильность нашей собственной реализации идентификации.

Выучить больше

Наш внутренний механизм рабочего процесса разрабатывается полностью с открытым исходным кодом и лицензируется MIT. Узнайте больше о нашем видении автоматизации цифровых процессов на основе OpenAPI.

Следите за развитием на Linkedin или Medium

Первоначально опубликовано на https://futurice.com.