1. 概要
Cloud Run にサービスをデプロイするデフォルトの手順を変更してセキュリティを強化し、デプロイされたアプリに安全にアクセスする方法を確認します。このアプリは、Cymbal Eats アプリケーションの「パートナー登録サービス」です。Cymbal Eats と連携して食事の注文を処理する企業が使用します。
学習内容
アプリを Cloud Run にデプロイするための最小のデフォルト手順を少し変更するだけで、セキュリティを大幅に強化できます。既存のアプリとデプロイ手順を取得し、デプロイ手順を変更して、デプロイされたアプリのセキュリティを強化します。
次に、アプリへのアクセスを承認し、承認済みリクエストを行う方法について説明します。
ここでは、アプリケーションのデプロイ セキュリティを網羅的に説明するのではなく、今後のすべてのアプリのデプロイメントに適用できる変更について説明します。これらの変更により、ほとんど手間をかけずにセキュリティを強化できます。
2. 設定と要件
セルフペース型の環境設定
- Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。
- プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列です。この場所はいつでも更新できます。
- プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud コンソールでは一意の文字列が自動生成されます。通常は、この内容を意識する必要はありません。ほとんどの Codelab では、プロジェクト ID(通常は
PROJECT_ID
と識別されます)を参照する必要があります。生成された ID が好みではない場合は、ランダムに別の ID を生成できます。または、ご自身で試して、利用可能かどうかを確認することもできます。このステップ以降は変更できず、プロジェクトを通して同じ ID になります。 - なお、3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
- 次に、Cloud のリソースや API を使用するために、Cloud コンソールで課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルの終了後に請求が発生しないようにリソースをシャットダウンするには、作成したリソースを削除するか、プロジェクト全体を削除します。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。
Cloud Shell をアクティブにする
- Cloud Console で、[Cloud Shell をアクティブにする] をクリックします。
Cloud Shell を初めて起動した場合は、その内容を説明する画面が(スクロールしなければ見えない位置に)表示されます。その場合は、[続行] をクリックしてください(以後表示されなくなります)。この中間画面は次のようになります。
Cloud Shell のプロビジョニングと接続に少し時間がかかる程度です。
この仮想マシンには、必要な開発ツールがすべて用意されています。仮想マシンは Google Cloud で稼働し、永続的なホーム ディレクトリが 5 GB 用意されているため、ネットワークのパフォーマンスと認証が大幅に向上しています。このコードラボでの作業のほとんどは、ブラウザまたは Chromebook から実行できます。
Cloud Shell に接続すると、すでに認証は完了しており、プロジェクトに各自のプロジェクト ID が設定されていることがわかります。
- 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`
- Cloud Shell で次のコマンドを実行して、gcloud コマンドがプロジェクトを認識していることを確認します。
gcloud config list project
コマンド出力
[core] project = <PROJECT_ID>
上記のようになっていない場合は、次のコマンドで設定できます。
gcloud config set project <PROJECT_ID>
コマンド出力
Updated property [core/project].
環境設定
このラボでは、Cloud Shell コマンドラインからコマンドを実行します。通常は、コマンドをコピーしてそのまま貼り付けることができますが、プレースホルダの値を正しいものに変更する必要がある場合もあります。
- 後のコマンドで使用するために、環境変数をプロジェクト ID に設定します。
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=partner-registration-service
- アプリを実行する Cloud Run サービス API、NoSQL データ ストレージを提供する Firestore API、デプロイ コマンドによって使用される Cloud Build API、ビルド時にアプリケーション コンテナの保持に使用される Artifact Registry を有効にします。
gcloud services enable \
run.googleapis.com \
firestore.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com
- ネイティブ モードで Firestore データベースを初期化します。このコマンドは App Engine API を使用するため、まず有効にする必要があります。
このコマンドでは、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
- サンプルアプリ リポジトリのクローンを作成してディレクトリに移動する
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 にデプロイします。このステップを変更して、次の 2 つの重要な方法でデプロイのセキュリティを強化します。
- 未認証のアクセスは許可しないでください。探索中に試すために許可することは便利ですが、これは商業パートナーが使用するウェブサービスであり、常にユーザーを認証する必要があります。
- アプリケーションが、必要以上の API とリソースへのアクセス権を持つ可能性のあるデフォルトのサービス アカウントではなく、必要な権限のみが付与された専用のサービス アカウントを使用しなければならないことを指定します。これは最小権限の原則と呼ばれ、アプリケーション セキュリティの基本コンセプトです。
ステップ 6 ~ 11 - サンプルのウェブリクエストを実行して、正しい動作を確認する
アプリケーションのデプロイに認証が必要になったため、これらのリクエストにはリクエスト元の身元証明書を含める必要があります。これらのファイルを変更する代わりに、コマンドラインから直接リクエストを送信します。
4. サービスを安全にデプロイする
deploy.sh
スクリプトでは、未認証のアクセスを許可しないことと、最小限の権限を持つ専用のサービス アカウントを使用することの 2 つの変更が必要と判断されました。
まず、新しいサービス アカウントを作成します。次に、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 を自分のプロジェクトの ID に修正します。
以下の手順で、最初の 2 行を削除し、他の 3 行を変更します。
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
curl
コマンドの -i
フラグは、出力にレスポンス ヘッダーを含めるように指示します。出力の最初の行は次のようになります。
HTTP/2 403
アプリは、認証されていないリクエストを拒否するオプションでデプロイされました。この curl コマンドには認証情報が含まれていないため、Cloud Run によって拒否されます。実際にデプロイされたアプリケーションは、このリクエストを実行したり、データを受信したりしません。
5. 認証済みリクエストを送信する
デプロイされたアプリはウェブ リクエストによって呼び出されます。このリクエストを Cloud Run が許可するには、認証する必要があります。ウェブリクエストは、次の形式の Authorization
ヘッダーを含めることで認証されます。
Authorization: Bearer
identity-token
ID トークンは、信頼できる認証プロバイダによって発行される、暗号で署名された短期間有効なエンコード文字列です。この場合、有効期限内の有効な Google 発行の ID トークンが必要です。
ユーザー アカウントとしてリクエストする
Google Cloud CLI ツールは、デフォルトの認証済みユーザーのトークンを提供できます。次のコマンドを実行して、自分のアカウントの ID トークンを取得し、ID_TOKEN 環境変数に保存します。
export ID_TOKEN=$(gcloud auth print-identity-token)
デフォルトでは、Google が発行する ID トークンの有効期間は 1 時間です。次の curl コマンドを実行して、承認されていないために拒否されたリクエストを実行します。このコマンドには、必要なヘッダーが含まれます。
curl -i -X GET $SERVICE_URL/partners \
-H "Authorization: Bearer $ID_TOKEN"
コマンドの出力は HTTP/2 200
で始まり、リクエストが受け入れられ、処理されていることを示します。(1 時間待ってからこのリクエストを再度試しても、トークンの有効期限が切れているため失敗します)。レスポンスの本文は、出力の末尾の空行の後に表示されます。
{"status":"success","data":[]}
パートナーはまだいません。
ディレクトリ内のサンプル JSON データを使用して、2 つの 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"
登録済みの 2 つのパートナーに関する情報が記載された、はるかに多くのコンテンツを含む JSON データが表示されます。
不正なアカウントとしてリクエストを行う
前の手順で行った認証済みリクエストが成功したのは、認証されただけでなく、認証されたユーザー(自分のアカウント)が承認されたためです。つまり、アカウントにはアプリを呼び出す権限がありました。認証されたすべてのアカウントが、この権限を付与されるわけではありません。
前のリクエストで使用されたデフォルト アカウントは、アプリを含むプロジェクトを作成したアカウントであるため承認されています。デフォルトでは、このアカウントにはアカウント内の Cloud Run アプリケーションを呼び出す権限が付与されています。この権限は必要に応じて取り消すことができます。これは本番環境のアプリで望ましいものです。権限やロールが割り当てられていない新しいサービス アカウントを作成し、そのサービス アカウントを使用してデプロイされたアプリにアクセスします。
tester
というサービス アカウントを作成します。
gcloud iam service-accounts create tester
- この新しいアカウントの ID トークンは、先ほどデフォルト アカウントの ID トークンを取得した方法とほぼ同じ方法で取得できます。ただし、この場合、デフォルト アカウントにサービス アカウントの権限を借用する権限が必要です。アカウントにこの権限を付与します。
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
- 次のコマンドを実行して、この新しいアカウントの ID トークンを TEST_IDENTITY 環境変数に保存します。コマンドにエラー メッセージが表示された場合は、1 ~ 2 分待ってからもう一度お試しください。
export TEST_TOKEN=$( \
gcloud auth print-identity-token \
--impersonate-service-account \
"tester@$PROJECT_ID.iam.gserviceaccount.com" \
)
- 前と同じように認証済みウェブリクエストを行いますが、この ID トークンを使用します。
curl -i -X GET $SERVICE_URL/partners \
-H "Authorization: Bearer $TEST_TOKEN"
リクエストは認証されているものの認可されていないため、コマンド出力は再び HTTP/2 403
で始まります。新しいサービス アカウントには、このアプリを呼び出す権限がありません。
アカウントの承認
ユーザーまたはサービス アカウントが Cloud Run サービスに対してリクエストを送信するには、そのユーザーまたはサービス アカウントに Cloud Run サービスに対する Cloud Run 起動元のロールが付与されている必要があります。次のコマンドを使用して、テスターのサービス アカウントにそのロールを付与します。
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}
新しいロールが更新されるまで 1 ~ 2 分待ってから、認証済みリクエストを繰り返します。最初の保存から 1 時間以上経過している場合は、新しい 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 リクエストは、通常のウェブページを含むウェブブラウザを使用して行うことはできません。ユーザーがリンクをクリックするか、ボタンをクリックしてウェブページでフォームを送信しても、ブラウザは、認証されたリクエストに Cloud Run で必要な Authorization
ヘッダーを追加しません。
Cloud Run の組み込み認証メカニズムは、エンドユーザーではなくプログラムが使用することを目的としています。
注:
Cloud Run はユーザー向けのウェブ アプリケーションをホストできますが、そのようなアプリケーションでは、ユーザーのウェブブラウザからの未認証リクエストを許可するように Cloud Run を設定する必要があります。アプリケーションでユーザー認証が必要な場合は、Cloud Run に認証を依頼するのではなく、アプリケーションで認証を処理する必要があります。アプリケーションは、Cloud Run の外部にあるウェブ アプリケーションと同じ方法でこれを実行できます。その方法については、この Codelab の範囲外です。
これまでのサンプル リクエストのレスポンスは、ウェブページではなく JSON オブジェクトでした。これは、このパートナー登録サービスはプログラムが使用することを目的としており、JSON はプログラムが使用しやすい形式であるためです。次に、このデータを使用するためのプログラムを作成して実行します。
Python プログラムからの認証済みリクエスト
プログラムは、標準の HTTP ウェブリクエストを介して、Authorization
ヘッダーを含む、保護された Cloud Run アプリケーションの認証済みリクエストを実行できます。これらのプログラムで新たに必要となるのは、そのヘッダーに配置する有効で有効期限切れでない ID トークンを取得することだけです。このトークンは、Google Cloud Identity and Access Management(IAM)を使用して Cloud Run によって検証されるため、IAM で認識されている機関によってトークンが発行され、署名されている必要があります。多くの言語で利用できるクライアント ライブラリがあり、プログラムはこれらのライブラリを使用して、このようなトークンの発行をリクエストできます。この例で使用するクライアント ライブラリは Python google.auth
です。一般的なウェブリクエストを行うための Python ライブラリはいくつかありますが、この例では一般的な requests
モジュールを使用します。
まず、次の 2 つのクライアント ライブラリをインストールします。
pip install google-auth
pip install requests
デフォルト ユーザーの ID トークンをリクエストする 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
コマンドで認証されたユーザーです。ユーザーがまだログインしたことがない場合、デフォルトのユーザーが存在しないため、このコードは失敗します。
別のプログラムにリクエストを送信するプログラムでは、通常、ユーザーの ID ではなく、リクエスト元のプログラムの ID を使用します。そのような場合のために、サービス アカウントがあります。Cloud Firestore などの API リクエストの実行時に使用する ID を提供する専用のサービス アカウントを使用して、Cloud Run サービスをデプロイしました。プログラムが Google Cloud Platform で実行されると、クライアント ライブラリは、割り当てられたサービス アカウントをデフォルトの ID として自動的に使用するため、どちらの状況でも同じプログラム コードが機能します。
Authorization ヘッダーを追加してリクエストを行う Python コードは次のとおりです。
auth_header = {"Authorization": "Bearer " + identity_token}
response = requests.get(url, headers=auth_header)
次の完全な Python プログラムは、Cloud Run サービスに認証済みリクエストを送信して、登録済みのすべてのパートナーを取得し、名前と割り当てられた ID を出力します。次のコマンドをコピーして実行し、このコードを 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 サービスに到達したのは、プログラムがユーザーの ID で認証されたためです。ユーザーはこのプロジェクトの所有者であるため、デフォルトで実行を許可されています。通常、このプログラムはサービス アカウントの ID で実行されます。Cloud Run や App Engine などのほとんどの Google Cloud プロダクトで実行する場合、デフォルトの ID はサービス アカウントであり、個人アカウントの代わりに使用されます。
7. 完了
お疲れさまでした。これでこの Codelab は終了です。
次のステップ:
Cymbal Eats の他の Codelab をご確認ください。
- Eventarc を使用して Cloud Workflows をトリガーする
- Cloud Storage からイベント処理をトリガーする
- Cloud Run から Private CloudSQL に接続する
- Cloud Run からフルマネージド データベースに接続する
- Identity-Aware Proxy(IAP)でサーバーレス アプリケーションを保護する
- Cloud Scheduler による Cloud Run ジョブのトリガー
- Cloud Run 上り(内向き)トラフィックの保護
- GKE Autopilot からプライベート AlloyDB に接続する
クリーンアップ
このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。
プロジェクトの削除
課金を停止する最も簡単な方法は、チュートリアル用に作成したプロジェクトを削除することです。