Добрый день, сегодня мы развернем serverless инфраструктуру на базе AWS lambda для загрузки изображений (или любых файлов) с хранением в приватном AWS S3 bucket. Использовать мы будем terraform скрипты, залитые и доступные в моем репозитории kompotkot/hatchery на GitHub. У изложенного подхода следующие преимущества: lambda вызывается по запросу и если данный функционал не является ключевым для вашего приложения, то позволяет экономить на содержании сервера у функций lambda изолированная среда работы, что идеально подходит для задач обработки загружаемых файлов. В случае загрузки вредоносного кода, атакующий не сможет выйти за пределы песочницы, сессия которой, в свою очередь, будет принудительно завершена спустя определенное время хранение файлов на S3 bucket крайне дешевое удовольствие Для примера мы будем использовать абстрактное приложение для ведения заметок с API. В каждую заметку мы можем загрузить картинку, а структура аналогична директории с файлами: У нашего гипотетического API имеется эндпоинт для получения заметки (entry) в журнале (journal): Если в ответ на этот эндпоинт status_code равен 200, значит пользователь авторизован и имеет доступ к данному журналу. Соответственно мы позволим ему сохранить картинки для этой заметки.  Во избежание добавления дополнительной таблицы в БД, которая нам обязательно потребуется для хранения записей о том, какой заметке, какая картинка принадлежит, мы воспользуемся resources от Bugout.dev. Данный подход использован в целях упрощения нашей инфраструктуры, но если необходимо, то данный шаг можно заменить на создание новой таблицы в вашей БД и написание API для сохранения, изменения и удаления записей о сохраняемых картинках. Код Bugout.dev открыт и вы можете ознакомиться документацией API ресурсов в репозитории на GitHub. Нам понадобится аккаунт и группа под названием myapp (название может быть любым в зависимости от вашего проекта) на странице Bugout.dev Teams и сохраним ID этой группы для следующего шага (в нашем случае, это e6006d97-0551-4ec9-aabd-da51ee437909): Далее создадим Bugout.dev Application под нашу группу myappс помощью curl запроса, токен можно сгенерировать на странице Bugout.dev Tokens, и сохраним его под переменной BUGOUT_ACCESS_TOKEN: В ответ мы получим подтверждение, об успешно созданном приложении: Данный ID f0a1672d-4659-49f6-bc51-8a0aad17e979 мы будем использовать для хранения resources, где каждый ресурс - это метаданные загружаемой картинки. Структура задается в произвольной форме в зависимости от необходимых ключей, в нашем случае она будет выглядеть следующим образом: Итого мы получаем своего рода удаленную БД, где мы будем каждый раз писать во время загрузки картинки в S3 bucket о том в какой журнал (journal_id) и в какую заметку (entry_id) была добавлена картинка с каким ID, названием и расширением. На AWS будут храниться картинки в S3 bucket и функционировать сервер на lambda для манипуляций с картинками. Нам понадобиться аккаунт AWS и настроенный IAM пользователь для terraform. Это аккаунт типа Programmatic access с доступом ко всем ресурсам без возможно работы с веб консолью: Нам понадобятся ключи, для этого добавьте в свое окружение следующие переменные: Также развернем свою VPC с подсетями: 2 приватного доступа 2 публичного доступа Они пригодятся для настройки AWS Load Balancer. Код данного модуля находится в files_distributor/network, отредактировав переменные в файле variables.tf запустим скрипт: Из вывода добавьте в переменные окружения значения для AWS_HATCHERY_VPC_ID, AWS_HATCHERY_SUBNET_PUBLIC_A_ID и AWS_HATCHERY_SUBNET_PUBLIC_B_ID. В нашем проекте мы будем использовать простую AWS lambda функцию. По опыту использования, я заметил, что при превышении размера пакета с кодом свыше 10МБ, резко падает скорость загрузки кода на AWS. Даже при предварительной заливке на S3 bucket и последующем создании lambda из него, AWS может подвистуть на продолжительный срок. Поэтому если вам необходимы сторонние библиотеки, то имеет смысл воспользоваться lambda layers. Так же, если вы планируете обойтись без сторонних библиотек с легковесным кодом в связке с CloudFront, то имеет смысл присмотреться к lambda@edge. Полная версия кода представлена в файле lambda_function.py в репозитории. На мой взгляд, эффективнее работать на nodejs, но для детальной работы с файлами мы воспользуемся python. Код состоит из основных блоков: Расширим базовое исключение, чтобы проксировать ответ от Bugout.dev Resources. Например, если картинки не существует, то при запросе необходимого ресурса, нам вернется 404 ошибка, что мы в свою очередь и вернем клиенту, как ответ на запрос о несуществующей картинке. Для сохранения картинки в S3 bucket воспользуемся стандартной библиотекой cgi, позволяющей нам распарсить тело запроса переданное в формате multipart/<image_type>. И сохранять картинки будем с указанием пути {journal_id}/entries/{entry_id}/images/{image_id} без указания расширения и названия файла. Во время извлечения картинки из S3 bucket нам потребуется закодировать в base64 для корректной передачи. Код функции lambda_handler(event,context) доступен на GitHub по ссылке, где происходит следующее: В начале мы проверяем, что запрос правильного формата и содержит journal_id и entry_id Выполняем запрос к API нашего гипотетического приложения https://api.example.com/journals/{journal_id}/entries/{entry_id} с заголовком авторизации headers={"authorization": auth_bearer_header} Далее в зависимости от метода запроса: GET, POST или DELETE мы читаем, загружаем или удаляем картинку для заметки в журнале Во время добавления файла в S3 bucket, мы проверяем расширение и размер файла. Данный функционал можно расширить проверкой хэша, чтобы избежать загрузки одинаковых файлов и тд. Далее нам потребуется упаковать в lambda библиотеку requests, к счастью boto3 для работы с AWS функционалом доступна из коробки lambda функции. Создадим пустое python окружение, установим библиотеку и упакуем содержимое site-packages: Поместим созданный архив lambda_function.zip в директорию files_distributor/bucket/modules/s3_bucket/files и добавим код самой lambda функции: Наш сервер готов, теперь можно загружать код на AWS и разворачивать lambda сервер, для чего воспользуемся скриптом в files_distributor/bucket: В итоге мы получим: Приватный AWS S3 bucket hatchery-sources, где хранится код для lambda функции Приватный AWS S3 bucket hatchery-files куда мы будем сохранять наши картинки с префиксом dev AWS lambda функцию с рабочим кодом сервера IAM роль для lambda, позволяющий писать в конкретный S3 bucket и логи Правила для IAM роли находятся в files_distributor/bucket/modules/iam/files/iam_role_lambda_inline_policy.json. Другой файл iam_role_lambda_policy.json необходим для корректного функционирования lambda функции.  Для дебага lambda достаточно добавить print необходимых значений или воспользоваться стандартным пакетом logging в python. Вывод выполнения каждого вызова функции доступен в AWS CloudWatch: После создания функции добавьте переменную BUGOUT_FILES_APPLICATION_ID из нашего кода в lambda окружение, что можно сделать во вкладке Configuration/Environment variables. Для последнего шага сохраните AWS lambda arn в переменную AWS_HATCHERY_LAMBDA_ARN. Теперь остался один шаг, создать AWS Security Group, где мы создадим порт на котором будет слушать AWS Load Balancer для последующей передачи данных в lambda функцию (в нашем случае это 80 и 443). Поздравляю, наша AWS lambda функция открыта миру и готова загружать и отдавать картинки для нашего приложения с заметками! DevOps, Web3, Информационная безопасность