呼び出しを認証する

認証された Cloud Functions を呼び出すには、基盤となるプリンシパルが次の要件を満たしている必要があります。

  • 関数を呼び出す権限を持っている。
  • 関数を呼び出すときに ID トークンを提供する。

プリンシパルとは何でしょうか。Cloud Functions の保護で説明されているように、Cloud Functions は次の 2 種類の ID をサポートしています。これらはプリンシパルとも呼ばれます。

  • サービス アカウント: 関数、アプリケーション、VM など、人以外の ID として機能する特別なアカウントです。これにより、人以外の認証を行うことができます。
  • ユーザー アカウント: 個々の Google アカウント所有者、または Google グループのような Google が管理するエンティティの一員など、人を表します。

基本的な IAM コンセプトの詳細については、IAM の概要をご覧ください。

認証された Cloud Functions の関数を呼び出すには、プリンシパルに呼び出し元の IAM 権限が必要です。

  • 第 1 世代の関数の場合: cloudfunctions.functions.invoke。これは通常、Cloud Functions 起動元のロールによって行われます。
  • 第 2 世代の関数の場合: run.routes.invoke。これは通常、Cloud Run 起動元ロールによって行われます。この権限は、Cloud Run サービス リソースに割り当てる必要があります。

これらの権限を付与するには、関数呼び出しに対する関数の認証で示されているように、add-invoker-policy-binding コマンドを使用します。

関数でその他の管理アクションを作成、更新、実行するには、プリンシパルに適切なロールが割り当てられている必要があります。ロールには、プリンシパルが実行できるアクションを定義する権限が含まれています。詳細については、IAM を使用したアクセスの承認をご覧ください。

関数の呼び出しは、さらに複雑になる可能性があります。イベント ドリブン関数を呼び出すことができるのは、関数が登録されているイベントソースだけですが、HTTP 関数は、異なる場所で発生するさまざまな種類の ID によって呼び出すことができます。呼び出し元は、関数やその関数を使用したい別の関数またはサービスをテストするデベロッパーである可能性があります。デフォルトでは、これらの ID は、自身を認証するためのリクエストとともに ID トークンを提供する必要があります。また、使用するアカウントに適切な権限が付与されている必要もあります。

詳しくは、ID トークンの生成と使用をご覧ください。

認証の例

このセクションでは、呼び出しの認証の例をいくつか示します。

例 1: デベロッパー テストの認証

デベロッパーは、関数を作成、更新、削除するためのアクセス権が必要になります。また、アクセス権は通常の(IAM)プロセスを使用して付与されます。

ただし、デベロッパーは、テスト目的で関数を呼び出す必要がある場合もあります。curl などのツールを使用して関数を呼び出すには、次の点にご注意ください。

  • 呼び出し権限を含む Cloud Functions ユーザー アカウントにロールを割り当てます。

    • 第 1 世代の関数の場合: cloudfunctions.functions.invoke。これは通常、Cloud Functions 起動元のロールによって行われます。デフォルトでは、Cloud Functions 管理者と Cloud Functions デベロッパーのロールにこの権限が含まれています。ロールとそれに関連する権限の詳細リストについては、Cloud Functions IAM のロールをご覧ください。
    • 第 2 世代の関数の場合: run.routes.invoke。これは通常、Cloud Run 起動元ロールによって行われます。
  • ローカルマシンから作業している場合は、Google Cloud CLI を初期化してコマンドライン アクセスを設定します。

  • Authorization ヘッダーに保存されている Google 生成の ID トークンとして認証情報をリクエストに提供します。たとえば、gcloud を使用して ID トークンを取得するには、次のコマンドを実行します。

    curl  -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
      https://FUNCTION_URL

    ここで、FUNCTION_URL は関数の URL です。この URL は、Google Cloud コンソールの Cloud Functions ページから取得するか、Google Cloud CLI のデプロイ コマンドの例の最初のステップで説明されている gcloud functions describe コマンドを実行して取得します。

呼び出す関数に対する cloudfunctions.functions.invoke 権限がアカウントに付与されていれば、gcloud によって作成されたトークンを使用して、どのプロジェクトでも HTTP 関数を呼び出すことができます。開発目的では、gcloud で生成された ID トークンを使用します。ただし、このようなトークンにはオーディエンス クレームがないため、リレー攻撃を受けやすくなります。本番環境では、適切な対象を指定したサービス アカウントに対して発行された ID トークンを使用します。この方法では、トークンの使用を目的のサービスのみに制限することでセキュリティを強化します。

通常どおり、関数の開発と使用に必要な最小限の権限セットを割り当てることをおすすめします。関数の IAM ポリシーは、必要最小限のユーザーとサービス アカウントに限定してください。

例 2: 関数呼び出しに対する関数の認証

複数の関数を接続するサービスをビルドする場合は、それぞれの関数が他の関数の特定のサブセットにのみリクエストを送信できるようにすることをおすすめします。たとえば、login 関数の場合、user profiles 関数へのアクセスは許可しますが、search 関数へのアクセスは許可しません。

特定の呼び出し側関数からのリクエストを受信するように受信側関数を構成するには、受信側の関数で、呼び出し側関数のサービス アカウントに適切な起動元ロールを付与する必要があります。第 1 世代の関数の場合、起動元のロールは Cloud Functions 起動元(roles/cloudfunctions.invoker)です。第 2 世代の関数の場合は Cloud Run 起動元(roles/run.invoker)で、基盤となるサービスで付与される必要があります。

Cloud Functions(第 1 世代)

コンソール

第 1 世代の関数の場合は、Cloud Functions 起動元を使用します。

  1. Google Cloud コンソールに移動します。

    Google Cloud コンソールに移動

  2. 受信側関数の横にあるチェックボックスをオンにします(関数自体はクリックしないでください)。

  3. 画面の上部の [権限] をクリックします。[権限] パネルが開きます。

  4. [プリンシパルを追加] をクリックします。

  5. [新しいプリンシパル] フィールドに、呼び出し側関数の ID を入力します。これはサービス アカウントのメールアドレスです。

  6. [ロールを選択] プルダウン メニューから Cloud Functions > Cloud Functions 起動元ロールを選択します。

  7. [保存] をクリックします。

gcloud

gcloud functions add-invoker-policy-binding コマンドを使用します。

gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:CALLING_FUNCTION_IDENTITY'

add-invoker-policy-binding コマンドは、指定されたメンバー(プリンシパル)が指定された関数を呼び出すことを許可する、呼び出し元ロールの IAM ポリシー バインディングを追加します。Google Cloud CLI は、関数の世代を自動的に検出し、適切な呼び出し元ロール(第 1 世代の場合は cloudfunctions.invoker、第 2 世代の場合は run.invoker)を追加します。

次のように置き換えます。

  • RECEIVING_FUNCTION: 受信関数の名前。
  • CALLING_FUNCTION_IDENTITY: 呼び出し元関数の ID(サービス アカウントのメールアドレス)。

Cloud Functions(第 2 世代)

コンソール

第 2 世代の関数の場合は、Cloud Run 起動元を使用します。

  1. Google Cloud コンソールに移動します。

    Google Cloud コンソールに移動

  2. Cloud Run サービスリストで、受信側関数の横にあるチェックボックスをオンにします(関数自体はクリックしないでください)。

    [権限] パネルが開きます。

  3. [プリンシパルを追加] をクリックします。

  4. 呼び出し側のサービスの ID を入力します。これは通常、メールアドレスになります(デフォルトは PROJECT_NUMBER-compute@developer.gserviceaccount.com)。

    プロジェクト番号は、プロジェクト ID やプロジェクト名とは異なることに注意してください。プロジェクト番号は、Google Cloud コンソールの [ダッシュボード] ページで確認できます。

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

  6. [保存] をクリックします。

gcloud

gcloud functions add-invoker-policy-binding コマンドを使用します。

gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:CALLING_FUNCTION_IDENTITY'

add-invoker-policy-binding コマンドは、指定されたメンバー(プリンシパル)が指定された関数を呼び出すことを許可する、呼び出し元ロールの IAM ポリシー バインディングを追加します。Google Cloud CLI は、関数の世代を自動的に検出し、適切な呼び出し元ロール(第 1 世代の場合は cloudfunctions.invoker、第 2 世代の場合は run.invoker)を追加します。

次のように置き換えます。

  • RECEIVING_FUNCTION: 受信関数の名前。
  • CALLING_FUNCTION_IDENTITY: 呼び出し元関数の ID(サービス アカウントのメールアドレス)。

この呼び出し側関数は受信側関数を呼び出すため、認証用に Google によって署名された ID トークンも備えている必要があります。これには、次の 2 つの手順を行います。

  1. audience フィールド(aud)に受信側関数の URL を設定して、Google によって署名された ID トークンを作成します。

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

このプロセスを管理する最も簡単かつ信頼性の高い方法は、以下に示すように認証ライブラリを使用してこのトークンを生成し、適用することです。

ID トークンの生成

このセクションでは、プリンシパルが関数を呼び出すために必要な ID トークンを生成するさまざまな方法について説明します。

ID トークンなしの未認証アクセスは可能ですが、有効にする必要があります。詳しくは、IAM を使用したアクセスの承認をご覧ください。

プログラムでのトークンの生成

次のコードは、ID トークンを生成した後、そのトークンを使用して Cloud Functions の関数を呼び出します。このコードは、ライブラリが認証情報を取得できる環境で動作します。これには、ローカルのアプリケーションのデフォルト認証情報をサポートする環境も含まれます。

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */

// Cloud Functions uses your function's url as the `targetAudience` value
// const targetAudience = 'https://project-region-projectid.cloudfunctions.net/myFunction';
// For Cloud Functions, endpoint (`url`) and `targetAudience` should be equal
// const url = targetAudience;

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);

  // Alternatively, one can use `client.idTokenProvider.fetchIdToken`
  // to return the ID Token.
  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(endpoint, audience):
    """
    make_authorized_get_request makes a GET request to the specified HTTP endpoint
    by authenticating with the ID token obtained from the google-auth client library
    using the specified audience value.
    """

    # Cloud Functions uses your function's URL as the `audience` value
    # audience = https://project-region-projectid.cloudfunctions.net/myFunction
    # For Cloud Functions, `endpoint` and `audience` should be equal

    req = urllib.request.Request(endpoint)

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

    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 using audience `audience`.
func makeGetRequest(w io.Writer, targetURL string, audience string) error {
	// For Cloud Functions, endpoint (`serviceUrl`) and `audience` are the same.
	// Example `audience` value (Cloud Functions): https://<PROJECT>-<REGION>-<PROJECT_ID>.cloudfunctions.net/myFunction
	// (`targetURL` and `audience` will differ for GET parameters)
	ctx := context.Background()

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

	resp, err := client.Get(targetURL)
	if err != nil {
		return fmt.Errorf("client.Get: %w", err)
	}
	defer resp.Body.Close()
	if _, err := io.Copy(w, resp.Body); err != nil {
		return fmt.Errorf("io.Copy: %w", 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 using the specified `audience`.
  //
  // For Cloud Functions, endpoint (`serviceUrl`) and `audience` are the same.
  // Example `audience` value (Cloud Functions): https://project-region-projectid.cloudfunctions.net/myFunction
  public static HttpResponse makeGetRequest(String serviceUrl, String audience) 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(audience)
            .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();
  }
}

手動でのトークンの生成

関数呼び出しの際になんらかの理由で認証ライブラリを使用できない場合は、Compute Metadata Server を使用する方法、または自己署名 JWT を作成して Google によって署名された ID トークンと交換する方法のいずれかを使用して手動で ID トークンを取得できます。

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

次のように、Compute Metadata Server を使用して、特定のユーザーの ID トークンを取得します。

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

AUDIENCE は、呼び出す関数の URL に置き換えます。この URL は、前述のデベロッパー テストの認証セクションで説明されている方法で取得できます。

Google によって署名された ID トークンと自己署名 JWT の交換

  1. 受信側の関数で、呼び出し側関数のサービス アカウントに Cloud Functions 起動元(roles/cloudfunctions.invoker)ロールを付与します。

  2. サービス アカウントとキーを作成し、呼び出し側関数またはサービスがリクエストを発信するホストに、秘密鍵(JSON 形式)を含むファイルをダウンロードします。

  3. ヘッダーを {"alg":"RS256","typ":"JWT"} に設定して JWT を作成します。ペイロードには、受信側関数の URL に設定された target_audience クレームと、上述のサービス アカウントのメールアドレスに設定された iss クレームと sub クレームを含める必要があります。また、exp クレームと iat クレームも含める必要があります。aud クレームは https://www.googleapis.com/oauth2/v4/token に設定する必要があります。

  4. 先ほどダウンロードした秘密鍵を使用して、JWT に署名します。

  5. この JWT を使用して、https://www.googleapis.com/oauth2/v4/token に POST リクエストを送信します。認証データはリクエストのヘッダーと本文に含める必要があります。

    ヘッダー内:

    Authorization: Bearer $JWT - where $JWT is the JWT you just created
    Content-Type: application/x-www-form-urlencoded
    

    本文内:

    grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$JWT
    

    $JWT は、作成した JWT で置き換えます

    これにより、Google によって署名された id_token を含む別の JWT が返されます。

GET/POST リクエストを受信側関数に送信します。Google によって署名された ID トークンをリクエストの Authorization: Bearer ID_TOKEN_JWT ヘッダーに含めます。

次のステップ