為叫用進行驗證 (第 1 代)

如要叫用已驗證的 Cloud Run 函式,基礎使用者主體必須符合下列規定:

  • 具備叫用函式的權限。
  • 在叫用函式時提供 ID 權杖。

什麼是本金?如保護 Cloud Run 函式的安全一文所述,Cloud Run 函式支援兩種不同的身分,也稱為使用者主體

  • 服務帳戶:這是用於非人類身分的特殊帳戶,例如函式、應用程式或 VM。這些屬性可讓您驗證非人類。
  • 使用者帳戶:這些帳戶代表使用者,可能是個別 Google 帳戶持有人,或是 Google 控管實體 (例如 Google 群組) 的一部分。

請參閱身分與存取權管理總覽,進一步瞭解基本身分與存取權管理概念。

如要叫用已經驗證的 Cloud Run 函式,主體必須具備叫用者 IAM 權限

如要授予這些權限,請使用 add-invoker-policy-binding 指令,如為函式呼叫驗證函式所示。

如要取得在函式上建立、更新或執行其他管理動作的權限,主體必須具備適當的角色。角色包含權限,可定義主體可執行的動作。詳情請參閱「使用 IAM 授予存取權」。

呼叫函式可能會帶來額外的複雜性。事件驅動函式只能由訂閱的事件來源叫用,但HTTP 函式可以由不同類型的身分 (來自不同來源) 叫用。叫用者可能是測試函式或其他函式或服務的開發人員,而這些函式或服務希望使用該函式。根據預設,這些身分必須在要求中提供ID 權杖,以便驗證自身身分。此外,使用中的帳戶也必須具備適當的權限。

進一步瞭解如何產生及使用 ID 權杖

驗證範例

本節將說明幾種不同的喚出程序驗證範例。

範例 1:驗證開發人員測試

開發人員需要建立、更新及刪除函式的存取權,這項權限會透過一般 (IAM) 程序授予。

不過,開發人員可能需要叫用函式進行測試。如要使用 curl 或類似工具叫用函式,請注意下列事項:

  • 為含有叫用權限的 Cloud Run 函式使用者帳戶指派角色。

    • cloudfunctions.functions.invoke。這通常是透過叫用者角色完成。根據預設,「Cloud Run 函式管理員」和「Cloud Run 函式開發人員」角色具有這項權限。如需角色和相關權限的完整清單,請參閱 Cloud Run 函式 IAM 角色
  • 如果您是使用本機電腦工作,請初始化 Google Cloud CLI,以便設定指令列存取權。

  • 請在要求中提供驗證憑證,做為 Google 產生的 ID 權杖,並儲存在 Authorization 標頭中。舉例來說,您可以執行下列指令,使用 gcloud 取得 ID 權杖:

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

    其中 FUNCTION_URL 是函式的網址。您可以從Google Cloud 控制台的 Cloud Run 函式頁面擷取這個網址,也可以執行 gcloud functions describe 指令,如 Google Cloud CLI 部署指令範例的第一個步驟所示。

只要您的帳戶對要叫用的函式擁有 cloudfunctions.functions.invoke 權限,即可使用 gcloud 建立的符記,在任何專案中叫用 HTTP 函式。如要進行開發,請使用 gcloud 產生的 ID 權杖。不過請注意,這類符記缺乏目標對象聲明,因此容易遭到中繼攻擊。在實際工作環境中,請使用服務帳戶核發的 ID 權杖,並指定適當的目標對象。這種做法可將權杖用途限制在指定服務,進而提升安全性。

一如往常,建議您分配開發及使用函式所需的最低權限組合。請確認函式上的 IAM 政策僅限於最少數的使用者和服務帳戶。

範例 2:驗證函式對函式呼叫

建構連線到多個函式的服務時,建議您確保每個函式只能向其他函式的特定子集傳送要求。舉例來說,如果您有 login 函式,則該函式應該能夠存取 user profiles 函式,但可能無法存取 search 函式。

如要將接收函式設為接受特定呼叫函式的要求,您必須將適當的叫用者角色授予接收函式中的呼叫函式服務帳戶。叫用者角色為 Cloud Run 叫用者 (roles/cloudfunctions.invoker):

控制台

使用 Cloud Run functions 叫用者:

  1. 前往 Google Cloud 控制台:

    前往 Google Cloud 控制台

  2. 按一下接收函式旁的核取方塊。(請勿按一下函式本身)。

  3. 按一下畫面頂端的「權限」,「Permissions」面板會隨即開啟。

  4. 按一下「新增主體」

  5. 在「New principals」欄位中輸入呼叫函式的身分。這應該是服務帳戶的電子郵件地址。

  6. 從「請選擇角色」下拉式選單中,依序選取「Cloud Functions」>「Cloud Functions Invoker」角色。

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

更改下列內容:

  • RECEIVING_FUNCTION:接收函式的名稱。
  • CALLING_FUNCTION_IDENTITY:呼叫函式身分,服務帳戶電子郵件。

由於呼叫函式會叫用接收函式,因此必須提供 Google 簽署的 ID 權杖來進行驗證。這個程序分為兩個步驟:

  1. 建立 Google 簽署的 ID 符記,其中目標對象欄位 (aud) 設為接收函式的網址。

  2. 在函式要求的 Authorization: Bearer ID_TOKEN 標頭中加入 ID 符記。

目前最簡單可靠的管理方式,就是使用驗證程式庫 (如以下所示) 產生及使用此權杖。

產生 ID 權杖

本節說明產生實體需要用來叫用函式的 ID 權杖的不同方法。

您可以啟用不需 ID 權杖的未經驗證存取權,但必須先啟用這項功能。詳情請參閱「使用 IAM 授予存取權」。

透過程式產生權杖

以下程式碼產生 ID 權杖後,會代您使用該權杖呼叫 Cloud Run 函式。這個程式碼適用於程式庫可取得驗證憑證的任何環境,包括支援本機應用程式預設憑證的環境。

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.fetch(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();
  }
}

手動產生權杖

如果您在叫用函式時,因某些原因無法使用驗證程式庫,則可以透過兩種方式手動取得 ID 權杖:使用 Compute 中繼資料伺服器,或建立自行簽署的 JWT,然後兌換為 Google 簽署的 ID 權杖。

使用中繼資料伺服器

您可以使用 Compute 中繼資料伺服器,擷取特定目標對象的 ID 權杖,如下所示:

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

AUDIENCE 替換為要叫用的函式網址。您可以按照上方「驗證開發人員測試」一節的說明,擷取這個網址。

使用自行簽署的 JWT 交換 Google 簽署的 ID 符記

  1. 將叫用者 (roles/cloudfunctions.invoker) 角色授予接收函式的呼叫函式服務帳戶。

  2. 建立服務帳戶和金鑰,然後將內含私密金鑰的檔案 (以 JSON 格式) 下載至呼叫函式或服務提出要求的主機。

  3. 建立 JWT,並將標頭設為 {"alg":"RS256","typ":"JWT"}。酬載應包含 target_audience 要求,並設為接收函式的網址,以及 isssub 要求,並設為上述所用服務帳戶的電子郵件地址。也應包含 expiat 宣告。aud 版權聲明應設為 https://www.googleapis.com/oauth2/v4/token

  4. 使用上述下載的私密金鑰簽署 JWT。

  5. 使用這個 JWT,傳送 POST 要求至 https://www.googleapis.com/oauth2/v4/token。驗證資料必須包含在要求的標頭和主體中。

    在頁首中:

    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

    這會傳回另一個 JWT,其中包含 Google 簽署的 id_token

將 GET/POST 要求傳送至接收函式。在要求的 Authorization: Bearer ID_TOKEN_JWT 標頭中加入 Google 簽署的 ID 符記。

後續步驟