【问题标题】:Securely calling a Google Cloud Function via a Google Apps Script通过 Google Apps 脚本安全地调用 Google Cloud 函数
【发布时间】:2020-05-13 17:48:37
【问题描述】:

如何通过 Google Apps 脚本安全地调用 Google Cloud Function?

✅我有一个 Google Cloud Function,我可以通过 https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION 访问它,并且我希望允许某些用户通过 Apps 脚本调用它。

✅ 为了保护 Cloud Function,我已将 Cloud Function Invoker 设置为仅包含已知电子邮件(例如 USER@COMPANY.com,这是一个有效的 Google 电子邮件)。

✅ 我可以通过 curl 成功调用 Cloud Function,同时使用此电子邮件登录 gcloud,方法是运行:curl https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION -H "Authorization: Bearer $(gcloud auth print-identity-token)"

✅ 我已在我的 Apps 脚本清单中授予以下 oauthScopes:

  • "https://www.googleapis.com/auth/script.external_request"
  • "https://www.googleapis.com/auth/userinfo.email"
  • "https://www.googleapis.com/auth/cloud-platform"

⛔️ 但是,当我尝试通过 Google Apps 脚本调用 Cloud Function 时,在使用电子邮件 USER@COMPANY.com 登录时,我无法调用它,而是返回了 401。这是我尝试调用的方式云函数:

const token = ScriptApp.getIdentityToken();
const options = {
  headers: {'Authorization': 'Bearer ' + token}
}
UrlFetchApp.fetch("https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION", options);

ℹ️我也尝试了以下方法:

  • 使用ScriptApp.getOAuthToken()
  • 添加额外的 oauthScopes,例如openid
  • 创建一个 OAuth 客户端 ID,并将 https://script.google.com 设置为授权的 Javascript 源。
  • 部署 Apps 脚本。
  • 彻底绝望地向天空呼喊

【问题讨论】:

  • 您需要同步回答吗?您每 100 分钟的通话次数是否可能超过 16 个?
  • @guillaumeblaquiere - 我认为 HTTP 请求不可能有同步的答案,我也不需要;不,这个调用不可能每 100 分钟有超过 16 个调用。

标签: google-apps-script oauth oauth-2.0 google-cloud-functions google-oauth


【解决方案1】:

我非常费力地从 Apps 脚本进行身份验证以调用 Cloud Run 应用程序,但我只是想通了,我相信调用任何 Google Cloud 应用程序(包括 Cloud Functions)都是类似的。本质上,目标是使用您已经以用户身份运行 Apps 脚本的身份验证信息调用受 Google Cloud IAM 保护的 HTTP 方法。

我认为缺少的步骤是,您使用的技术只有在 Apps Script 脚本和 Google Cloud Function(或在我的情况下为 Run 容器)在同一个 GCP 项目中时才有效。 (查看如何associate the script with the GCP project。)

以这种方式进行设置比其他方式简单得多:当您将脚本与 GCP 项目相关联时,这会自动为项目创建一个 OAuth Client ID 配置,并且 Apps 脚本的 getIdentityToken 函数返回一个身份令牌,该令牌仅对该客户端 ID 有效(它被编码到令牌的 aud field 字段中)。如果您想要一个适用于另一个项目的身份令牌,则需要另一种方式。

如果您能够将脚本和 GCP 函数或应用程序放在同一个 GCP 项目中,那么您还必须做这些事情,其中​​很多事情您已经做过:

  • 通过curl https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION -H "Authorization: Bearer $(gcloud auth print-identity-token)" 成功测试您的云功能的身份验证(按照here 的指示)。如果此操作失败,那么您遇到的问题与此 Stack Overflow 问题中的问题不同,因此我将省略此问题的故障排除步骤。

  • 确保您实际上是 who the script is running as。您无法从电子表格中的自定义函数获取身份令牌,因为它们是匿名运行的。在其他情况下,Apps 脚本代码可能以其他人的身份运行,例如某些触发器。

  • 按照here 的说明重新部署 Cloud Function(或按照here 的说明重新部署 Cloud Run 容器),以便应用获取任何新的客户端 ID 配置。这是在创建任何新客户端 ID 后必需的,包括通过将脚本添加或重新添加到 GCP 项目而自动创建的客户端 ID。 (如果您将脚本移到另一个 GCP 项目,然后再次将其移回,它似乎创建了另一个 Client ID 而不是重用旧的,旧的将停止工作。)

  • manifest 中显式添加"openid" scope(以及所有其他需要的范围,例如https://www.googleapis.com/auth/script.external_request)。 getIdentityToken() 将返回 null 而没有可能导致此错误的 openid 范围。读者注意:仔细阅读这个要点 - 范围名称实际上只是“openid” - 它不像其他范围那样是 URL。

    "oauthScopes": ["openid", "https://...", ...]
    
  • 使用getIdentityToken(),不要使用getOAuthToken()。根据我的阅读, getOAuthToken() 返回访问令牌而不是身份令牌。访问令牌不能证明您的身份;而他们只是给予证明授权来访问某些资源。

如果您无法将脚本添加到与 GCP 应用程序相同的项目中,我不知道该怎么做,因为我从未成功尝试过。通常,您的任务是获取与您的 GCP 客户端 ID 之一相关的 OAuth 身份令牌。我不认为一个应用程序(或 GCP 项目)应该能够为不同的 OAuth 应用程序(不同的 GCP 项目)获取身份令牌。无论如何,它仍然是可能的。 Google 在其OpenID Connect docs 中讨论了高级别 OAuth 身份验证。如果您让用户单击重定向链接,因为 Apps 脚本不允许浏览器重定向,那么 HTML serviceweb client 一起执行常规 Google sign-in flow 可能适用于用户呈现操作。如果您只是需要保护您的服务不被公开,也许您可​​以尝试其他涉及服务帐户的身份验证选项。 (我也没有尝试过。)如果服务只需要知道用户是谁,也许您可​​以解析身份令牌并将用户的标识符作为请求的一部分发送。如果服务需要访问他们的 Google 资源,那么也许您可以让用户单独登录到该应用程序,并通常使用 OAuth 来长期访问他们的资源,并在被 Apps 脚本调用时根据需要使用它。

【讨论】:

  • 这是一个非常有用的答案。我使用类似的过程来设置安全的云功能。对我来说,关键步骤是“重新部署云功能”。在检查了所有其他身份验证凭据后,我今天来到这里,但我忘了重新部署! (我总是忘记这一步,因为云功能似乎会在运行时检查身份验证权限 - 但它只检查部署?)
  • 嗨,亚历山大,感谢您的回答。我设法开始处理一个 Google 表格文件,但我的项目的工作方式是,人们必须制作一份 google 表格的副本,然后才能继续使用 auth 调用 Cloud 函数。一旦我使用原始文件授权的同一用户复制文件,它就不起作用。我收到 401 错误。有任何想法吗?我在这方面花了很多时间,并且可以让它发挥作用......谢谢
  • 我回来了。再次遇到同样的问题并找到我的旧评论。对于复制文件,您必须将新的(复制的)文件添加到同一个项目中,然后部署该功能。这是一个巨大的痛苦,但我有时会忘记其中一个步骤。 1)添加用户IAM权限 2)将脚本文件添加到同一个云项目 3)1和2完成后重新部署函数
【解决方案2】:

上面的答案很好。但是由于我是新手,所以我仍然不得不花费大量时间来弄清楚。

这对我有用:

应用脚本代码:

async function callCloudFunction() {
  const token = ScriptApp.getIdentityToken();
  const options = {
    headers: {'Authorization': 'Bearer ' + token}
  }
  const data = JSON.parse(await UrlFetchApp.fetch("https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION", options).getContentText())
  return data
}
  • 确保在项目配置中,您的函数与创建函数的项目相同。
  • 之后,您可以在函数的“权限”部分添加要访问脚本的用户的电子邮件。
  • 正如@alexander-taylor 所提到的,确保将范围添加到您的清单文件中。您可以在应用程序脚本的配置选项卡中使清单可见。我也花了一些时间才弄明白。

【讨论】:

    【解决方案3】:

    感谢您的评论,您可以做两件事。但在此之前,您必须知道您不能(或者至少我从来没有做到这一点)创建一个有效的身份令牌,以便使用用户凭据由 Cloud Function 和 Cloud Run 进行身份验证。 I opened a question on this

    但是您可以使用用户凭据调用 Google Cloud API!所以

    【讨论】:

      猜你喜欢
      • 2014-04-30
      • 1970-01-01
      • 2012-11-04
      • 2022-01-05
      • 2020-05-20
      • 1970-01-01
      • 1970-01-01
      • 2019-02-01
      • 1970-01-01
      相关资源
      最近更新 更多