通过Google Apps脚本安全地调用Google Cloud Function [英] Securely calling a Google Cloud Function via a Google Apps Script
问题描述
如何通过Google Apps脚本安全地调用Google Cloud Function?
How can I securely call a Google Cloud Function via a Google Apps Script?
✅我有一个Google Cloud Function,可以在https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION
上访问它,并且我希望允许某些用户通过Apps脚本进行调用.
✅ I have a Google Cloud Function, which I can access at https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION
, and which I would like to allow certain users to invoke via an Apps Script.
✅为了保护Cloud Function,我将Cloud Function Invoker设置为仅包含已知电子邮件(例如USER@COMPANY.com
,这是有效的Google电子邮件).
✅ To secure the Cloud Function, I have set Cloud Function Invoker to only include known email (e.g. USER@COMPANY.com
, where this is a valid Google email).
✅通过运行curl https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION -H "Authorization: Bearer $(gcloud auth print-identity-token)"
,我可以通过curl成功调用Cloud Function,同时使用此电子邮件登录到gcloud.
✅ I am able to successfully invoke the Cloud Function via curl, while logged into gcloud with this email, by running: curl https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION -H "Authorization: Bearer $(gcloud auth print-identity-token)"
.
✅我已在我的Apps脚本的清单中授予以下oauthScopes:
✅ I have granted the following oauthScopes in my Apps Script's manifest:
-
"https://www.googleapis.com/auth/script.external_request"
-
"https://www.googleapis.com/auth/userinfo.email"
-
"https://www.googleapis.com/auth/cloud-platform"
"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.这是我尝试调用的方式云功能:
⛔️ However, when I attempt to invoke the Cloud Function via a Google Apps Script, while logged in with the email USER@COMPANY.com
, I am unable to invoke it and instead returned a 401. Here is how I have attempted to invoke the Cloud Function:
const token = ScriptApp.getIdentityToken();
const options = {
headers: {'Authorization': 'Bearer ' + token}
}
UrlFetchApp.fetch("https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION", options);
ℹ️我也尝试了以下方法:
ℹ️ I have also tried the following:
- 使用
ScriptApp.getOAuthToken()
- 添加其他oauthScopes,例如
openid
. - 使用设置为
https://script.google.com
的OAuth客户端ID创建OAuth客户端ID. - 部署Apps脚本.
- 大声疾呼,绝望绝望
- Using
ScriptApp.getOAuthToken()
- Adding additional oauthScopes, e.g.
openid
. - Creating an OAuth Client ID with
https://script.google.com
set as an Authorized Javascript origin. - Deploying the Apps Script.
- Crying out to the sky in utter, abject despair
推荐答案
我很难通过Apps脚本进行身份验证来调用Cloud Run应用程序,并且只是想出了办法,而且我认为调用任何包含Cloud的Google Cloud应用程序都非常相似功能.本质上,目标是使用已经运行的身份验证信息来调用受 Google Cloud IAM 保护的HTTP方法以用户身份使用Apps脚本.
I struggled very much authenticating from Apps Script to invoke a Cloud Run application and just figured it out, and I believe it's similar for calling any Google Cloud application including Cloud Functions. Essentially the goal is to invoke an HTTP method protected by Google Cloud IAM using the authentication information you already have running Apps Script as the user.
我认为缺少的步骤是,仅当Apps脚本脚本和Google Cloud Function(在我的情况下为Run容器)在同一个GCP项目中时,您使用的技术才有效. (请参阅如何将该脚本与GCP项目关联.)
The missing step I believe is that the technique you're using will only work if the Apps Script script and Google Cloud Function (or Run container in my case) are in the same GCP project. (See how to associate the script with the GCP project.)
通过这种方式进行设置比其他方式要简单得多:将脚本与GCP项目相关联时,这会自动创建将OAuth客户端ID 配置为项目,并且Apps Script的getIdentityToken
函数返回仅对该客户端ID有效的身份令牌(已编码为
Setting it up this way is much simpler than otherwise: when you associate the script with a GCP project, this automatically creates an OAuth Client ID configuration to the project, and Apps Script's getIdentityToken
function returns an identity token that is only valid for that client ID (it's coded into the aud
field field of the token). If you wanted an identity token that works for another project, you'd need to get one another way.
如果您能够将脚本和GCP函数或应用程序放在同一个GCP项目中,那么您还必须做这些事情,其中许多事情您已经做过了:
If you are able to put the script and GCP function or app in the same GCP project, you'll also have to do these things, many of which you already did:
-
通过
curl https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION -H "Authorization: Bearer $(gcloud auth print-identity-token)"
成功测试了您的云功能的身份验证(按照此处).如果失败了,那么您遇到的问题与该堆栈溢出问题所提出的问题不同,因此我将为此排除故障排除步骤.
Successfully test authentication of your cloud function via
curl https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION -H "Authorization: Bearer $(gcloud auth print-identity-token)"
(as instructed here). If this fails then you have a different problem than is asked in this Stack Overflow question, so I'm omitting troubleshooting steps for this.
确保您实际上是脚本的运行方式.您无法从电子表格中的自定义功能获取身份令牌,因为它们是匿名运行的.在其他情况下,Apps脚本代码可能会以其他人的身份(例如某些触发器)运行.
Ensure you are actually who the script is running as. You cannot get an identity token from custom function in a spreadsheet as they run anonymously. In other cases, the Apps Script code may be running as someone else, such as certain triggers.
按此处(或类似方式)中所述重新部署云功能按此处)重新部署Cloud Run容器,以便该应用选择设置任何新的客户端ID配置.在创建任何新的客户ID(包括通过将脚本添加或重新添加到GCP项目中自动创建的ID)之后,这是必需的. (如果将脚本移至另一个GCP项目,然后再次移回,则似乎是在创建另一个客户端ID,而不是重复使用旧的ID,而旧的ID将停止工作.)
Redeploy the Cloud Function as mentioned here (or similarly redeploy the Cloud Run container as mentioned here) so the app will pick up any new Client ID configuration. This is required after any new Client ID is created, including the one created automatically by adding or re-adding the script to the GCP project. (If you move the script to another GCP project and then move it back again, it seems to create another Client ID rather than reuse the old one and the old one will stop working.)
添加"openid
"范围 (以及所有其他必需的范围,例如https://www.googleapis.com/auth/script.external_request
)明确地在清单. getIdentityToken()
将返回没有openid
范围的null
,这可能导致此错误.读者注意:请仔细阅读此要点-范围名称实际上只是"openid
"-与其他范围不同,它不是URL.
Add the "openid
" scope (and all other needed scopes, such as https://www.googleapis.com/auth/script.external_request
) explicitly in the manifest. getIdentityToken()
will return null
without the openid
scope which can cause this error. Note to readers: read this bullet point carefully - the scope name is literally just "openid
" - it's not a URL like the other scopes.
"oauthScopes": ["openid", "https://...", ...]
使用 getIdentityToken()
,请勿使用getOAuthToken()
.根据我所读的内容,getOAuthToken()返回访问令牌而不是身份令牌.访问令牌不能证明您的身份;相反,他们只是授予访问某些资源的授权.
Use getIdentityToken()
and do NOT use getOAuthToken()
. According to what I've read, getOAuthToken() returns an access token rather than an identity token. Access tokens do not prove your identity; rather they just give prove authorization to access some resources.
如果您无法将脚本与GCP应用程序添加到同一项目中,由于我从未成功尝试过该脚本,因此我不知道该怎么办.通常,您的任务是获取与一个GCP客户端ID绑定的OAuth身份令牌.我认为一个应用程序(或GCP项目)不能为另一个OAuth应用程序(不同的GCP项目)获取身份令牌.无论如何,这仍然可能. Google在 OpenID Connect文档中高层讨论了OAuth身份验证.也许 HTML服务可以执行常规的网络客户端,如果您让用户单击重定向链接,因为Apps脚本不允许浏览器,它将适用于用户呈现的操作重定向.如果只需要保护您的服务免受公众侵害,也许您可以尝试其他涉及服务帐户的身份验证选项. (我也没有尝试过.)如果该服务只需要知道用户是谁,则也许您可以解析身份令牌并将该用户的标识符作为请求的一部分发送.如果该服务需要访问其Google资源,那么也许您可以让用户单独登录该应用,并通常使用OAuth长期访问其资源,并在Apps脚本调用时根据需要使用OAuth.
If you are not able to add the script to the same project as the GCP application, I don't know what to do as I've never successfully tried it. Generally you're tasked with obtaining an OAuth identity token tied to one of your GCP client ids. I don't think one app (or GCP project) is supposed to be able to obtain an identity token for a different OAuth app (different GCP project). Anyway, it may still be possible. Google discusses OAuth authentication at a high level in their OpenID Connect docs. Perhaps an HTML service to do a regular Google sign-in flow with a web client, would work for user-present operations if you get the user to click the redirect link as Apps Script doesn't allow browser redirects. If you just need to protect your service from the public, perhaps you could try other authentication options that involve service accounts. (I haven't tried this either.) If the service just needs to know who the user is, perhaps you could parse the identity token and send the identifier of the user as part of the request. If the service needs to access their Google resources, then maybe you could have the user sign in to that app separately and use OAuth generally for long term access to their resources, using it as needed when called by Apps Script.
这篇关于通过Google Apps脚本安全地调用Google Cloud Function的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!