进行身份验证以便调用

如需调用经过身份验证的 Cloud Run 函数,底层主账号必须满足以下要求:

  • 拥有调用该函数的权限。
  • 在调用函数时提供 ID 令牌。

什么是校长?如保护您的 Cloud Functions 中所述,Cloud Functions 支持两种不同的身份,这些身份也称为主账号

  • 服务账号:此类特殊账号充当非用户(例如函数、应用或虚拟机)的身份。它们为您提供了一种对非用户进行身份验证的方法。
  • 用户账号:这些账号代表作为个人 Google 账号持有人,或 Google 控制的实体(例如 Google 群组)的人员。

如需详细了解 IAM 基本概念,请参阅 IAM 概览

如需调用经过身份验证的 Cloud Run 函数,主账号必须拥有调用方 IAM 权限

  • run.routes.invoke。这通常通过 Cloud Run Invoker 角色授予。此权限必须在 Cloud Run 服务资源上分配。

要授予这些权限,请使用 add-invoker-policy-binding 命令,如对函数进行身份验证以进行函数调用中所示。

对于创建、更新函数或对函数执行其他管理操作的权限,主账号必须具有适当的角色。角色包含可定义允许主账号执行的操作的权限。如需了解详情,请参阅使用 IAM 授予访问权限

调用函数可能会带来额外的复杂性。事件驱动的函数只能由为其订阅的事件源调用,但 HTTP 函数可以由园子不同位置的不同种类的身份调用。调用方可以是正在测试该函数或想要使用该函数的其他函数或服务的开发者。默认情况下,这些身份必须为验证自己身份的请求提供 ID 令牌。此外,正在使用的账号还必须获得适当的权限。

详细了解如何生成和使用 ID 令牌

身份验证示例

本部分介绍了针对调用进行身份验证的不同示例。

示例 1:对开发者测试进行身份验证

作为开发者,您需要具备创建、更新和删除函数的权限,使用常规 (IAM) 流程便授予此权限。

但作为开发者,您可能需要调用函数以进行测试。如需使用 curl 或类似工具调用函数,请注意以下事项:

  • 为您的 Cloud Functions 用户账号分配可提供调用权限的角色。

  • 如果您使用的是本地机器,请通过初始化 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 Functions 页面中检索此网址,也可以运行 gcloud functions describe 命令检索此网址,如 Google Cloud CLI 部署命令示例的第一步所示。

只要您的账号对要调用的函数具有 cloudfunctions.functions.invoke 权限,您就可以使用 gcloud 创建的令牌在任何项目中调用 HTTP 函数。如果要进行开发,请使用 gcloud 生成的 ID 令牌。但请注意,此类令牌缺少目标对象声明,因此易遭受中继攻击。在生产环境中,请使用为服务账号颁发并指定了适当目标对象的 ID 令牌。这种方法通过将令牌的使用范围限制为预期服务来提高安全性。

与往常一样,我们建议您分配开发和使用函数所需的一组最小权限。请务必将您函数的 IAM 政策限制在最少数量的用户和服务账号。

示例 2:对函数进行身份验证以进行函数调用

在构建连接多个函数的服务时,最好确保每个函数只能向特定的某些其他函数发送请求。例如,如果您有一个 login 函数,那么该函数应该可以访问 user profiles 函数,但可能不应该访问 search 函数。

要将接收函数配置为接收来自特定调用函数的请求,您需要将适当的调用方角色授予接收函数的调用函数的服务账号。 调用方角色是 Cloud Run Invoker (roles/run.invoker),并且必须获得授予对底层服务的权限:

控制台

使用 Cloud Run Invoker:

  1. 前往 Google Cloud 控制台:

    转到 Google Cloud 控制台

  2. 在 Cloud Run 服务列表中,点击接收函数旁边的复选框。(请勿点击函数本身。)

    此时权限面板会打开。

  3. 点击添加主账号

  4. 输入调用服务的身份。默认情况下,通常是一个电子邮件地址 (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 会自动检测函数的世代,并添加正确的 Invoker 角色(对于 Cloud Run 函数,为 run.invoker)。

替换以下内容:

  • RECEIVING_FUNCTION:接收函数的名称。
  • CALLING_FUNCTION_IDENTITY:调用函数身份,即服务账号电子邮件。

由于调用函数将调用接收函数,因此调用函数还必须提供 Google 签名的 ID 令牌以进行身份验证。此流程分为两步:

  1. 创建 Google 签名的 ID 令牌,并将受众字段 (aud) 设置为接收函数的网址。

  2. 将 ID 令牌添加到函数请求的 Authorization: Bearer ID_TOKEN 标头中。

到目前为止,管理此过程最简单、最可靠的方式是通过如下所示的身份验证库生成并使用此令牌。

生成 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();
  }
}

手动生成令牌

如果您正在调用某个函数,并且出于某个原因无法使用身份验证库,您可以通过两种方法手动获取 ID 令牌:使用计算元数据服务器或创建一个自签名 JWT 并将其替换为 Google 签名的 ID 令牌。

使用元数据服务器

您可以使用计算元数据服务器提取具有特定目标设备的 ID 令牌,如下所示:

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

AUDIENCE 替换为要调用的函数的网址。您可以按照上文的对开发者进行身份验证测试部分中的说明检索此网址。

将自签名 JWT 替换成带有 Google 签名的 ID 令牌

  1. 在接收函数中,为调用函数的服务账号授予 Invoker (roles/cloudfunctions.invoker) 角色。

  2. 创建服务账号和密钥,并将包含私钥的 JSON 文件(JSON 格式)下载到调用函数或服务发起请求所在的主机。

  3. 创建一个 JWT,并将标头设置为 {"alg":"RS256","typ":"JWT"}。载荷应该包含设置为接收函数的网址的 target_audience 声明,以及设置为上面使用的服务账号的电子邮件地址的 isssub 声明。它还应该包含 expiat 声明。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

    系统即会返回另一个 JWT,其中包含由 Google 签名的 id_token

将您的 GET/POST 请求发送到接收函数。在请求的 Authorization: Bearer ID_TOKEN_JWT 标头中添加 Google 签名的 ID 令牌。

后续步骤