サービス間認証

アーキテクチャで複数のサービスを使用している場合、これらのサービスは同期または非同期で相互に通信する必要があります。これらのサービスの多くは非公開で、アクセスには認証情報が必要です。

非同期通信では、次の Google Cloud サービスを使用できます。

  • Cloud Tasks(1 対 1 の非同期通信を行う場合)。
  • Pub/Sub(1 対多の非同期通信を行う場合)。
  • Cloud Scheduler(定期的にスケジュール設定された非同期通信を行う場合)。

いずれの場合も、使用中のサービスが、設定した構成に基づいて受信側のサービスとの通信を管理します。

ただし、同期通信の場合は、サービスがエンドポイント URL を使用して HTTP 経由で別のサービスを直接呼び出します。このユースケースでは、各サービスが特定のサービスに対してのみリクエストを送信できるようにします。たとえば、login サービスの場合、user-profiles サービスにはアクセスできますが、search サービスにはアクセスできません。

この場合は、IAM と、その作業に必要な権限の最小セットが付与されたサービスごとのユーザー管理サービス アカウントに基づいてサービス ID を使用することをおすすめします。

さらに、リクエストで呼び出し側サービス ID の証明を提示する必要があります。これを行うには、Google 署名付き OpenID Connect ID トークンをリクエストの一部として追加するように呼び出し側サービスを構成します。

サービス アカウントを設定する

サービス アカウントを設定するには、呼び出し元のサービス アカウントを受信側サービスのメンバーにすることで、呼び出し側サービスからのリクエストを受け入れるように受信側サービスを構成します。そのサービス アカウントに Cloud Run 起動元(roles/run.invoker)のロールを付与します。該当するタブの手順に沿って、これらの作業を行います。

Console UI

  1. Google Cloud Console に移動します。

    Google Cloud Console に移動

  2. 受信側のサービスを選択します。

  3. 右上隅にある [情報パネルを表示] をクリックして、[権限] タブを表示します。

  4. [メンバーを追加] フィールドに、呼び出し側のサービスの ID を入力します。これは通常、デフォルトでメールアドレス PROJECT_NUMBER-compute@developer.gserviceaccount.com です。

  5. [ロールの選択] プルダウン メニューから、Cloud Run Invoker ロールを選択します。

  6. [追加] をクリックします。

gcloud

gcloud run services add-iam-policy-binding コマンドを使用します。

gcloud run services add-iam-policy-binding RECEIVING_SERVICE \
  --member='serviceAccount:CALLING_SERVICE_IDENTITY' \
  --role='roles/run.invoker'

ここでは、RECEIVING_SERVICE は受信側サービスの名前で、CALLING_SERVICE_IDENTITY はサービス アカウントのメールアドレス(デフォルトでは PROJECT_NUMBER-compute@developer.gserviceaccount.com)です。

ID トークンの取得と構成

呼び出し側のサービス アカウントに適切なロールを付与したら、次の操作を行う必要があります。

  1. 受信側のサービスの URL に設定されたオーディエンス クレーム(aud)を使用して、Google 署名付き ID トークンを取得します。

  2. 受信側サービスに対するリクエストの Authorization: Bearer ID_TOKEN ヘッダーに ID トークンを含めます。

このプロセスを最も簡単かつ信頼性の高い方法で管理するには、以下に示すように認証ライブラリを使用してこのトークンを生成し、適用します。このコードは、ライブラリによって認証情報が取得される Google Cloud 外のあらゆる環境で機能します(ローカル アプリケーションのデフォルト認証情報をサポートする環境を含む)。

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// Example: https://my-cloud-run-service.run.app/books/delete/12345
// const url = 'https://TARGET_HOSTNAME/TARGET_URL';

// Example (Cloud Run): https://my-cloud-run-service.run.app/
// Example (Cloud Functions): https://project-region-projectid.cloudfunctions.net/myFunction
// const targetAudience = 'https://TARGET_HOSTNAME/';
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();

async function request() {
  console.info(`request ${url} with target audience ${targetAudience}`);
  const client = await auth.getIdTokenClient(targetAudience);
  const res = await client.request({url});
  console.info(res.data);
}

request().catch(err => {
  console.error(err.message);
  process.exitCode = 1;
});

Python

import urllib

import google.auth.transport.requests
import google.oauth2.id_token

def make_authorized_get_request(service_url):
    """
    make_authorized_get_request makes a GET request to the specified HTTP endpoint
    in service_url (must be a complete URL) by authenticating with the
    ID token obtained from the google-auth client library.
    """

    req = urllib.request.Request(service_url)

    auth_req = google.auth.transport.requests.Request()
    id_token = google.oauth2.id_token.fetch_id_token(auth_req, service_url)

    req.add_header("Authorization", f"Bearer {id_token}")
    response = urllib.request.urlopen(req)

    return response.read()

Go


import (
	"context"
	"fmt"
	"io"

	"google.golang.org/api/idtoken"
)

// makeGetRequest makes a request to the provided targetURL with an authenticated client.
func makeGetRequest(w io.Writer, targetURL string) error {
	// functionURL := "https://TARGET_URL"
	ctx := context.Background()

	// client is a http.Client that automatically adds an "Authorization" header
	// to any requests made.
	client, err := idtoken.NewClient(ctx, targetURL)
	if err != nil {
		return fmt.Errorf("idtoken.NewClient: %v", err)
	}

	resp, err := client.Get(targetURL)
	if err != nil {
		return fmt.Errorf("client.Get: %v", err)
	}
	defer resp.Body.Close()
	if _, err := io.Copy(w, resp.Body); err != nil {
		return fmt.Errorf("io.Copy: %v", err)
	}

	return nil
}

Java

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.IdTokenCredentials;
import com.google.auth.oauth2.IdTokenProvider;
import java.io.IOException;

public class Authentication {

  // makeGetRequest makes a GET request to the specified Cloud Run or
  // Cloud Functions endpoint, serviceUrl (must be a complete URL), by
  // authenticating with an Id token retrieved from Application Default Credentials.
  public static HttpResponse makeGetRequest(String serviceUrl) throws IOException {
    GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
    if (!(credentials instanceof IdTokenProvider)) {
      throw new IllegalArgumentException("Credentials are not an instance of IdTokenProvider.");
    }
    IdTokenCredentials tokenCredential =
        IdTokenCredentials.newBuilder()
            .setIdTokenProvider((IdTokenProvider) credentials)
            .setTargetAudience(serviceUrl)
            .build();

    GenericUrl genericUrl = new GenericUrl(serviceUrl);
    HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(tokenCredential);
    HttpTransport transport = new NetHttpTransport();
    HttpRequest request = transport.createRequestFactory(adapter).buildGetRequest(genericUrl);
    return request.execute();
  }
}

メタデータ サーバーの使用

なんらかの理由で認証ライブラリを使用できない場合は、コンテナが Cloud Run で実行されている間、コンピューティング メタデータ サーバーから ID トークンを取得できます。この方法は、ローカルマシンからなど、Google Cloud の外部では機能しないことに注意してください。

curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=[AUDIENCE]" \
     -H "Metadata-Flavor: Google"

AUDIENCE は、起動しているサービスの URL です。

このサービス間の認証方式を使用するアプリケーションのエンドツーエンドのチュートリアルは、安全な Cloud Run サービスのチュートリアルをご覧ください。

GCP の外部から呼び出す

Google Cloud の外部からプライベートサービスを呼び出すには、前述のように、認証ライブラリを使用し、アプリケーションのデフォルト認証情報を設定するのが最善の方法です。

自己署名 JWT を使用して Google 署名付き ID トークンを取得できますが、作業はかなり複雑になり、エラーが発生しやすくなります。基本的な手順は次のとおりです。

  1. target_audience クレームを受信側サービスの URL に設定して、サービス アカウント JWT に自己署名します。

  2. Google によって署名された ID トークンと自己署名された JWT を交換します。このトークンでは、aud クレームに上の URL が設定されています。

  3. サービスに対するリクエストの Authorization: Bearer ID_TOKEN ヘッダーに ID トークンを含めます。

上記の手順のサンプルについては、この Cloud Functions の例で確認できます。