Мы используем бессерверные инструменты, такие как Google App Engine и Cloud Run, для ускорения разработки веб-приложений в сегодняшнем мире, насыщенном облаками.

Однако, с точки зрения «безопасности» в Google Cloud Platform (GCP), как мы можем безопасно вводить секреты в наши приложения? Можем ли мы с легкостью решить эти проблемы в GCP?

«Бессерверная» кардинально изменила способ размещения и обслуживания веб-сервисов. Не беспокоясь о базовой инфраструктуре (такой как физические серверы, операционные системы, сеть), веб-службы можно создавать с легкостью.

Однако абстрагирование базовой инфраструктуры не обходится без затрат.

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

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

С помощью Google Cloud Platform (GCP) указанную выше проблему можно решить разными способами. Давайте рассмотрим эти методы, чтобы решить нашу проблему внедрения секретов в нашу бессерверную среду.

Встречайте «бессерверных»

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

Для бессерверных инструментов я использовал как Cloud Run, так и Google App Engine Flex с настраиваемой средой выполнения.

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

Для Google App Engine он может варьироваться от стандартной версии, где параметры конфигурации сильно связаны с платформой, до версии Flex, где параметры более слабые и менее строгие. соединен с платформой. В Google App Engine Flex есть возможность использовать настраиваемую среду выполнения. По сравнению с другими вариантами среды выполнения, такими как Go, Python или Node, настраиваемая среда выполнения предлагает использование конфигурации Dockerfile. Это позволяет разработчику приложения выбирать время выполнения с очень низкой степенью связи с GCP, предлагая гибкость, аналогичную той, которая была показана выше для Cloud Run.

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

Итак, как мы можем безопасно вводить секреты в наши бессерверные инструменты в GCP

При использовании бессерверных инструментов важно отметить, что у нас нет прямого доступа к нашим серверам! Это означает, что традиционные средства развертывания с SSH невозможны. Фактически, с GCP вместо прямого доступа к серверам вы захотите использовать gcloud CLI.

Например, мы можем развертывать приложения с помощью следующих команд из интерфейса командной строки:

# Cloud Run
gcloud builds submit --tag gcr.io/$PROJECT_ID/$APP:$APP_VER
gcloud run deploy --image gcr.io/$PROJECT_ID/$APP:$APP_VER --allow-unauthenticated --platform="managed" --region="$REGION" $APP

# Google App Engine
gcloud app deploy --project $PROJECT_ID

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

Метод первый: «запекать» секреты в приложении

Один из первых способов, который я изучал, заключался в том, чтобы запечь секреты непосредственно в приложении. Это был простой метод для Google App Engine, где секретные файлы в том же каталоге, что и app.yaml, развертываются вместе с исходным кодом. После развертывания приложения к секретам можно будет получить доступ, прочитав эти файлы.

Для Cloud Run эквивалентом было бы копирование секретных файлов в образ Docker как часть инструкций Dockerfile. Когда контейнер развертывается из образа, файлы также будут присутствовать в среде.

Однако мне показалось, что этот метод не подходит по нескольким причинам:

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

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

Улучшение последнего метода с помощью хранилища управления ключами (KMS)

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

Давайте посмотрим, как мы можем использовать KMS в наших проектах GCP.

Чтобы включить KMS, вам нужно выбрать IAM & Admin > Cryptographic keys, как показано выше, в консоли GCP на боковой панели. Затем нажмите кнопку + Create Key Ring.

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

Как только ваше кольцо будет создано, нажмите на кольцо, а затем выберите опцию + Create Key. Задайте имя ключа и выберите любой из доступных вариантов, как показано выше (указанных выше параметров по умолчанию должно хватить, если вы собираетесь использовать ключи управления Google).

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

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

Хотя можно (и, возможно, проще!) Назначить авторизацию для всего проекта для шифрования и дешифрования файлов через меню IAM, я хотел вместо этого использовать другой способ ограничения назначенных разрешений. Это должно было гарантировать, что никакие учетные записи пользователей или служб не могут быть использованы для чтения или изменения других секретов, выходящих за рамки их первоначальных целей.

Для учетных записей пользователей мы хотим разрешить пользователям как читать, так и записывать секреты. Мы можем выполнить следующую команду (заменив все указанные ниже переменные) и назначить роль role / cloudkms.cryptoKeyEncrypterDecrypter, чтобы пользователи могли шифровать и расшифровывать секретные файлы.

gcloud kms keys add-iam-policy-binding ${KEY} --location=${LOCATION} --keyring=${KEY_RING} --member=user:${USER_ACCOUNT} --role=roles/cloudkms.cryptoKeyEncrypterDecrypter --project=${PROJECT}

Для учетных записей служб, используемых системой, мы можем захотеть ограничить их способностью расшифровывать только секретные файлы, поскольку им не нужно изменять содержимое секретов. Мы можем сделать это, выполнив следующую команду с включенной ролью roles / cloudkms.cryptoKeyDecrypter.

gcloud kms keys add-iam-policy-binding ${KEY} --location=${LOCATION} --keyring=${KEY_RING} --member=serviceAccount:${SERVICE_ACCOUNT} --role=roles/cloudkms.cryptoKeyDecrypter --project=${PROJECT}

В приведенном выше примере учетная запись службы может различаться для разных бессерверных инструментов, которые вы используете. Если название вашего проекта - fine-project, а его идентификатор - 12345678, служебная учетная запись по умолчанию для Cloud Run будет [email protected], а служебная учетная запись для App Engine будет [email protected]. Если вы не уверены в названии и идентификаторе своего проекта, проверьте своего менеджера ресурсов здесь.

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

# Decrypt from ${ENCRYPTED_FILE_NAME} to ${DECRYPTED_FILE_NAME}
gcloud kms decrypt --ciphertext-file=${ENCRYPTED_FILE_NAME} --plaintext-file=${DECRYPTED_FILE_NAME} --key=${KEY} --location=${LOCATION} --keyring=${KEY_RING} --project=${PROJECT}
# Encrypt from ${DECRYPTED_FILE_NAME} to ${ENCRYPTED_FILE_NAME}
gcloud kms encrypt --ciphertext-file=${ENCRYPTED_FILE_NAME} --plaintext-file=${DECRYPTED_FILE_NAME} --key=${KEY} --location=${LOCATION} --keyring=${KEY_RING} --project=${PROJECT}

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

Является ли KMS ответом на секреты выпечки?

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

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

Защита секретов с помощью другого инструмента - Secret Manager

Альтернативой методологии запекания секретов является извлечение секретов только во время развертывания. Для этого в GCP мы можем обратиться к его управляемому решению Secret Manager.

Как следует из названия, мы можем использовать Secret Manager для управления секретами. Управление здесь означает, что мы можем создавать, читать и уничтожать разные версии разных секретов. Используя Secret Manager, мы также можем использовать API для извлечения секретов во время сборки, развертывания или даже во время работы приложения. Это дает нам гибкость, когда мы хотим использовать секреты нашего приложения, избегая необходимости запекать секреты в приложении.

Чтобы начать использовать «Диспетчер секретов», выберите Security > Secret Manager на боковой панели и включите API, необходимый для его использования. Выберите опцию+ Create Secret, чтобы создать свой первый секрет!

Выберите имя для вашего секрета (это важно! Вам нужно будет использовать это, чтобы ссылаться на свой секрет позже). Вы также можете напрямую ввести секретное значение или загрузить текстовый файл, содержащий секретное значение.

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

gcloud secrets add-iam-policy-binding $SECRET_NAME --member=serviceAccount:${SERVICE_ACCOUNT} --role=roles/secretmanager.secretReader --project=${PROJECT}

Для авторизации сервисных учетных записей для доступа к созданным нами секретам нам необходимо выполнить указанную выше команду (после замены переменных фактическими значениями).

Обратите внимание, что для учетной записи службы это то же самое, что и для KMS. Чтобы скопировать из приведенного выше - если имя вашего проекта fine-project с идентификатором 12345678, служебная учетная запись по умолчанию для Cloud Run будет [email protected], а служебная учетная запись для App Engine будет [email protected]. Если вы не уверены в названии и идентификаторе своего проекта, проверьте своего менеджера ресурсов здесь.

Имея в наличии наши секреты и наши сервисные учетные записи, которые могут получать доступ к нашим секретам из бессерверных сред, давайте рассмотрим, как получить эти секреты. Обратите внимание, что мы используем библиотеку Node.js для достижения вышеуказанного, но то же самое можно сделать для различных языков, таких как Go, Ruby и Python, как показано здесь.

Из приведенного выше скрипта мы видим, что используются две библиотеки: nodejs-secret-manager и googleapis. Для модулей с других языков обращайтесь к официальному разработчику Github здесь.

Эти две библиотеки служат разным целям получения секрета. nodejs-secret-manager используется для получения секрета с секретным именем.

const client = new SecretManagerServiceClient();
const name = await getSecretName('MY SECRET NAME');

Обратите внимание, что «секретное имя» здесь включает не только имя, которое вы выбрали ранее, но также идентификатор проекта, в котором хранится ваш секрет, а также номер версии по секрету. Полное секретное имя должно иметь следующий формат:

projects/${project-id}/secrets/${secret-name}/versions/${version}

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

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

const auth = new google.auth.GoogleAuth({});
const projectId = await auth.getProjectId();

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

В противном случае для использования вне кода приложения мы также можем обратиться к интерфейсу командной строки gcloud.

gcloud beta secrets versions access ${version} --secret ${secret-key}

Обратите внимание, что этот управляемый инструмент все еще находится в стадии бета-тестирования на дату публикации этого сообщения - 2 марта 2020 года.

Заключение

В заключение мы рассмотрели два различных способа управления и развертывания секретов в наших бессерверных инструментах (Cloud Run и Google App Engine).

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

Затем мы перешли к изучению другого метода, используя Secret Manager. Вместо того, чтобы сохранять секреты в приложении и расшифровывать их во время развертывания, наша методология с Secret Manager позволяет нам извлекать секреты в любое время. Это дает дополнительное преимущество, заключающееся в том, что секреты не «запекаются» в приложении, что позволяет секретам и конфигурациям быть «обратно совместимыми» по мере их обновления.

Это два метода, которые я считаю полезными для развертывания моих секретов в бессерверных инструментах - Cloud Run и Google App Engine Flex в Customruntime. Надеюсь, вам понравилось это читать, и дайте мне знать, если у вас есть другие способы достижения этой же цели.

Чао ~