Безопасное развертывание в Cloud Run

1. Обзор

Вы измените стандартные шаги развертывания сервиса в Cloud Run для повышения безопасности, а затем узнаете, как безопасно получить доступ к развернутому приложению. Приложение представляет собой «сервис регистрации партнеров» приложения Cymbal Eats, который используется компаниями, сотрудничающими с Cymbal Eats для обработки заказов на еду.

Что вы узнаете

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

Далее вы увидите, как авторизовать доступ к приложению и отправлять авторизованные запросы.

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

2. Настройка и требования

Настройка среды для самостоятельного обучения

  1. Войдите в консоль Google Cloud и создайте новый проект или используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Название проекта — это отображаемое имя участников данного проекта. Это строка символов, не используемая API Google. Вы можете изменить её в любое время.
  • Идентификатор проекта уникален для всех проектов Google Cloud и является неизменяемым (его нельзя изменить после установки). Консоль Cloud автоматически генерирует уникальную строку; обычно вам неважно, какая она. В большинстве практических заданий вам потребуется указать идентификатор проекта (обычно он обозначается как PROJECT_ID ). Если сгенерированный идентификатор вас не устраивает, вы можете сгенерировать другой случайный идентификатор. В качестве альтернативы вы можете попробовать свой собственный и посмотреть, доступен ли он. После этого шага его нельзя изменить, и он останется неизменным на протяжении всего проекта.
  • К вашему сведению, существует третье значение — номер проекта , который используется некоторыми API. Подробнее обо всех трех значениях можно узнать в документации .
  1. Далее вам потребуется включить оплату в консоли Cloud для использования ресурсов/API Cloud. Выполнение этого практического задания не должно стоить дорого, если вообще что-либо. Чтобы отключить ресурсы и избежать дополнительных расходов после завершения этого урока, вы можете удалить созданные ресурсы или удалить весь проект. Новые пользователи Google Cloud имеют право на бесплатную пробную версию стоимостью 300 долларов США .

Активировать Cloud Shell

  1. В консоли Cloud нажмите «Активировать Cloud Shell» . 853e55310c205094.png .

55efc1aaa7a4d3ad.png

Если вы никогда раньше не запускали Cloud Shell, вам будет показан промежуточный экран (внизу), описывающий его назначение. В этом случае нажмите «Продолжить» (и вы больше никогда его не увидите). Вот как выглядит этот одноразовый экран:

9c92662c6a846a5c.png

Подготовка и подключение к Cloud Shell займут всего несколько минут.

9f0e51b578fecce5.png

Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Она предоставляет постоянный домашний каталог размером 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Большая часть, если не вся, работа в этом практическом задании может быть выполнена с помощью обычного браузера или вашего Chromebook.

После подключения к Cloud Shell вы увидите, что ваша аутентификация пройдена и что проект уже настроен на ваш идентификатор проекта.

  1. Выполните следующую команду в Cloud Shell, чтобы подтвердить свою аутентификацию:
gcloud auth list

вывод команды

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Выполните следующую команду в Cloud Shell, чтобы убедиться, что команда gcloud знает о вашем проекте:
gcloud config list project

вывод команды

[core]
project = <PROJECT_ID>

Если это не так, вы можете установить это с помощью следующей команды:

gcloud config set project <PROJECT_ID>

вывод команды

Updated property [core/project].

Настройка среды

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

  1. Установите переменную среды, указав идентификатор проекта, для использования в последующих командах:
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=partner-registration-service
  1. Включите API службы Cloud Run, которая будет запускать ваше приложение, API Firestore, который обеспечит хранение данных в NoSQL-базе данных, API Cloud Build, который будет использоваться командой развертывания, и реестр артефактов, который будет использоваться для хранения контейнера приложения после сборки:
gcloud services enable \
  run.googleapis.com \
  firestore.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com
  1. Инициализируйте базу данных Firestore в нативном режиме. Эта команда использует API App Engine, поэтому его необходимо предварительно включить.

В команде необходимо указать регион для App Engine (который мы использовать не будем, но должны создать по историческим причинам) и регион для базы данных. Для App Engine мы будем использовать us-central, а для базы данных — nam5 . nam5 — это многорегиональное расположение в США. Многорегиональное расположение обеспечивает максимальную доступность и отказоустойчивость базы данных.

gcloud services enable appengine.googleapis.com

gcloud app create --region=us-central
gcloud firestore databases create --region=nam5
  1. Клонируйте репозиторий с примерами приложений и перейдите в соответствующую директорию.
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git

cd cymbal-eats/partner-registration-service

3. Ознакомьтесь с файлом README.

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

Шаг 3 - Запустите команду npm install

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

Шаги 4 и 5 — Отредактируйте и запустите deploy.sh

Эти шаги развертывают приложение в Cloud Run, оставляя большинство параметров по умолчанию. Вы измените этот шаг, чтобы сделать развертывание более безопасным двумя ключевыми способами:

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

Шаги 6–11 — Выполните тестовые веб-запросы для проверки корректности работы.

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

4. Безопасное развертывание сервиса.

В скрипте deploy.sh были выявлены две необходимые изменения: запрет доступа неавторизованных пользователей и использование выделенной учетной записи службы с минимальными привилегиями.

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

Создайте учетную запись службы и предоставьте ей необходимый доступ к Firestore/Datastore.

gcloud iam service-accounts create partner-sa

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:partner-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role=roles/datastore.user

Отредактируйте deploy.sh

Измените файл deploy.sh , чтобы запретить неаутентифицированный доступ (–no-allow-unauthenticated) и указать новую учетную запись службы (–service-account) для развернутого приложения. Исправьте значение GOOGLE_PROJECT_ID, заменив его на идентификатор вашего проекта.

Вам нужно будет удалить первые две строки и изменить три другие строки, как показано ниже.

gcloud run deploy $SERVICE_NAME \
  --source . \
  --platform managed \
  --region ${REGION} \
  --no-allow-unauthenticated \
  --project=$PROJECT_ID \
  --service-account=partner-sa@${PROJECT_ID}.iam.gserviceaccount.com

Разверните сервис

Запустите скрипт deploy.sh из командной строки:

./deploy.sh

После завершения развертывания в последней строке вывода команды отобразится URL-адрес службы нового приложения. Сохраните этот URL-адрес в переменной среды:

export SERVICE_URL=<URL from last line of command output>

Теперь попробуйте получить заказ из приложения с помощью инструмента curl :

curl -i -X GET $SERVICE_URL/partners

Флаг -i для команды curl указывает на необходимость включения заголовков ответа в вывод. Первая строка вывода должна быть следующей:

HTTP/2 403

Приложение было развернуто с опцией запрета неаутентифицированных запросов. Эта команда curl не содержит информации для аутентификации, поэтому Cloud Run её отклоняет. Фактически развернутое приложение даже не запускается и не получает никаких данных из этого запроса.

5. Выполняйте аутентифицированные запросы.

Развернутое приложение запускается путем отправки веб-запросов, которые теперь должны быть аутентифицированы, чтобы Cloud Run мог их разрешить. Аутентификация веб-запросов осуществляется путем добавления заголовка Authorization следующего вида:

Authorization: Bearer identity-token носителя

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

Отправьте запрос от имени своей учетной записи пользователя.

Инструмент командной строки Google Cloud может предоставить токен для пользователя, прошедшего аутентификацию по умолчанию. Выполните эту команду, чтобы получить токен идентификации для своей учетной записи и сохранить его в переменной среды ID_TOKEN:

export ID_TOKEN=$(gcloud auth print-identity-token)

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

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $ID_TOKEN"

Вывод команды должен начинаться с HTTP/2 200 , что указывает на то, что запрос принят и обрабатывается. (Если вы подождете час и попытаетесь повторить этот запрос, он завершится неудачей, поскольку срок действия токена истечет.) Тело ответа находится в конце вывода, после пустой строки:

{"status":"success","data":[]}

Партнёров пока нет.

Зарегистрируйте партнеров, используя пример данных JSON из каталога, с помощью двух команд curl :

curl -X POST \
  -H "Authorization: Bearer $ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d "@example-partner.json" \
  $SERVICE_URL/partner

и

curl -X POST \
  -H "Authorization: Bearer $ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d "@example-partner2.json" \
  $SERVICE_URL/partner

Повторите предыдущий GET-запрос, чтобы увидеть всех зарегистрированных партнеров:

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $ID_TOKEN"

Вы должны увидеть данные в формате JSON с гораздо большим объемом информации, содержащей сведения о двух зарегистрированных партнерах.

Отправьте запрос от имени неавторизованной учетной записи.

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

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

  1. Создайте учетную запись службы с именем tester .
gcloud iam service-accounts create tester
  1. Для этой новой учетной записи вы получите токен идентификации точно так же, как и для вашей основной учетной записи ранее. Однако для этого вашей основной учетной записи необходимо разрешение на имитацию учетных записей служб. Предоставьте вашей учетной записи это разрешение.
export USER_EMAIL=$(gcloud config list account --format "value(core.account)")

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="user:$USER_EMAIL" \
  --role=roles/iam.serviceAccountTokenCreator
  1. Теперь выполните следующую команду, чтобы сохранить токен идентификации для этой новой учетной записи в переменной среды TEST_IDENTITY. Если команда выдаст сообщение об ошибке, подождите минуту-две и попробуйте снова.
export TEST_TOKEN=$( \
  gcloud auth print-identity-token \
    --impersonate-service-account \
    "tester@$PROJECT_ID.iam.gserviceaccount.com" \
)
  1. Выполните аутентифицированный веб-запрос, как и раньше, но используя следующий токен идентификации:
curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $TEST_TOKEN"

В выводе команды снова появится ошибка HTTP/2 403 поскольку запрос, хотя и аутентифицирован , не авторизован . Новая учетная запись службы не имеет разрешения на запуск этого приложения.

Авторизация учетной записи

Для отправки запросов к службе Cloud Run пользователь или учетная запись службы должны иметь роль Cloud Run Invoker. Предоставьте тестовой учетной записи службы эту роль с помощью команды:

export REGION=us-central1
gcloud run services add-iam-policy-binding ${SERVICE_NAME} \
  --member="serviceAccount:tester@$PROJECT_ID.iam.gserviceaccount.com" \
  --role=roles/run.invoker \
  --region=${REGION}

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

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $TEST_TOKEN"

Теперь вывод команды начинается с HTTP/1.1 200 OK , а последняя строка содержит JSON-ответ. Этот запрос был принят Cloud Run и обработан приложением.

6. Аутентификация программ против аутентификации пользователей.

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

Встроенный в Cloud Run механизм аутентификации предназначен для использования программами, а не конечными пользователями.

Примечание:

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

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

Аутентифицированные запросы из программы на Python.

Программа может отправлять аутентифицированные запросы к защищенному приложению Cloud Run через стандартные веб-запросы HTTP, но с добавлением заголовка Authorization . Единственная новая проблема для таких программ — получение действительного, непросроченного токена идентификации для размещения в этом заголовке. Этот токен будет проверен Cloud Run с помощью Google Cloud Identity and Access Management (IAM), поэтому токен должен быть выдан и подписан органом, признанным IAM. Существуют клиентские библиотеки на многих языках программирования, которые программы могут использовать для запроса выдачи такого токена. В этом примере будет использоваться библиотека google.auth для Python. Существует несколько библиотек Python для выполнения веб-запросов в целом; в этом примере используется популярный модуль requests .

Первым шагом является установка двух клиентских библиотек:

pip install google-auth
pip install requests

Код на Python для запроса токена идентификации для пользователя по умолчанию выглядит следующим образом:

credentials, _ = google.auth.default()
credentials.refresh(google.auth.transport.requests.Request())
identity_token = credentials.id_token

Если вы используете командную оболочку, такую ​​как Cloud Shell или стандартную терминальную оболочку на своем компьютере, то пользователем по умолчанию является тот, кто прошел аутентификацию в этой оболочке. В Cloud Shell это обычно пользователь, вошедший в Google. В других случаях это пользователь, прошедший аутентификацию с помощью gcloud auth login или другой команды gcloud . Если пользователь никогда не входил в систему, пользователя по умолчанию не будет, и этот код завершится ошибкой.

Для программ, отправляющих запросы к другим программам, обычно нежелательно использовать учетную запись пользователя, а следует использовать учетную запись запрашивающей программы. Для этого и существуют сервисные учетные записи. Вы развернули службу Cloud Run с выделенной сервисной учетной записью, которая предоставляет идентификатор, используемый при отправке запросов к API, например, к Cloud Firestore. Когда программа работает на платформе Google Cloud, клиентские библиотеки автоматически используют назначенную ей сервисную учетную запись в качестве идентификатора по умолчанию, поэтому один и тот же код программы работает в обоих случаях.

Код на Python для отправки запроса с добавлением заголовка Authorization выглядит следующим образом:

auth_header = {"Authorization": "Bearer " + identity_token}
response = requests.get(url, headers=auth_header)

Приведенная ниже программа на Python выполнит авторизованный запрос к сервису Cloud Run для получения списка всех зарегистрированных партнеров, а затем распечатает их имена и присвоенные идентификаторы. Скопируйте и запустите команду ниже, чтобы сохранить этот код в файл print_partners.py .

cat > ./print_partners.py << EOF
def print_partners():
    import google.auth
    import google.auth.transport.requests
    import requests

    credentials, _ = google.auth.default()
    credentials.refresh(google.auth.transport.requests.Request())
    identity_token = credentials.id_token

    auth_header = {"Authorization": "Bearer " + identity_token}
    response = requests.get("${SERVICE_URL}/partners", headers=auth_header)

    parsed_response = response.json()
    partners = parsed_response["data"]

    for partner in partners:
        print(f"{partner['partnerId']}: {partner['name']}")


print_partners()
EOF

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

gcloud auth application-default login

Следуйте инструкциям для завершения входа в систему. Затем запустите программу из командной строки:

python print_partners.py

В результате получится примерно следующее:

10102: Zippy food delivery
67292: Foodful

Запрос программы достиг сервиса Cloud Run, поскольку он был аутентифицирован с использованием вашей учетной записи, и вы являетесь владельцем этого проекта и, следовательно, по умолчанию имеете право его запускать. Чаще всего эта программа запускается под учетной записью сервиса. При запуске в большинстве продуктов Google Cloud, таких как Cloud Run или App Engine, по умолчанию используется учетная запись сервиса, а не личная учетная запись.

7. Поздравляем!

Поздравляем, вы завершили практическое занятие!

Что дальше:

Ознакомьтесь с другими обучающими материалами Cymbal Eats:

Уборка

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

Удаление проекта

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