授权从Firebase Cloud功能访问Google Cloud Translate



我正在尝试编写一个调用Google Cloud Translate的Firebase Cloud函数。我得到这个错误:

Error: 7 PERMISSION_DENIED: Cloud IAM permission 'cloudtranslate.generalModels.predict' denied. 

我的证书似乎没有从Firebase Cloud Function传递到Google Cloud Translate。我设置了一个用户管理的服务帐户,首先我尝试从CLI:进行部署

firebase deploy --only functions:ENtranslateES --service-account google-cloud-translate@my-awesome-app.iam.gserviceaccount.com

这引发了这个错误:

error: unknown option '--service-account'

然后我尝试了这个:

gcloud functions deploy ENtranslateES --service-account google-cloud-translate@my-awesome-app.iam.gserviceaccount.com

奏效了。我在CLI上得到了很长的响应,没有出现错误,我在谷歌云控制台中看到,云功能ENtranslateES最后一次部署是在我执行该命令时。

触发Firebase Cloud功能会继续返回PERMISSION_DENIED: Cloud IAM permission错误。

这是我的代码:

exports.ENtranslateES = functions.firestore.document('Users/{userID}/English/Translation_Request').onUpdate((change) => { 
const { TranslationServiceClient } = require('@google-cloud/translate').v3;
const translationClient = new TranslationServiceClient();
const projectId = 'my-awesome-app';
const location = 'global';
const text = 'Hello, world!';
async function translateText() {
const request = {
parent: `projects/${projectId}/locations/${location}`,
contents: [text],
mimeType: 'text/plain', // mime types: text/plain, text/html
sourceLanguageCode: 'en',
targetLanguageCode: 'es',
};
const [response] = await translationClient.translateText(request);
for (const translation of response.translations) {
console.log(`Translation: ${translation.translatedText}`);
}
}
return translateText()
});

我还设置了一个从Postman到Google Cloud Translate的POST查询。我输入了Client IDClient SecretAuth URLAccess Token URL等的Authorization属性。Postman查询有效。我应该把我的Client IDClient Secret等放在我的Firebase云函数代码中吗?从我所读到的内容来看,如果我使用服务帐户部署函数,这似乎是不必要的。

我成功了!以下是我学到的。

我使用IAM服务帐户将Firebase云功能连接到谷歌云翻译。这些说明也适用于谷歌云功能和其他谷歌云服务。

安全云功能概述称,云功能安全可以是基于身份的,也可以是基于网络的户帐户(用户名和密码)或服务帐户

IAM和服务帐户

谷歌云控制台一开始可能会让人不知所措。你从一个用户帐户开始,这个帐户可能是你的gmail地址。然后你建立了一个组织,在你的组织中你建立了项目。

然后查找IAM & AdminIAM代表身份访问管理。服务帐户看起来像电子邮件地址。这为Googleverse中的云功能提供了标识。换句话说,为您的云功能创建一个服务帐户可以使其与谷歌云中的其他内容进行交互。文档"功能标识"讨论了服务帐户的这一方面。

服务帐户具有主体角色。您的一个服务帐户应该是您的电子邮件地址,其中包含您的姓名和角色所有者。文档Using IAM to Authorize Access显示了如何向服务帐户授予主体和角色。

把其他服务帐户想象成你的小黄人,他们做你的出价。有些是在您注册谷歌云服务时自动创建的。

当你想做一些事情时,你可以创建新的服务帐户。例如,我写了一个Firebase Cloud Function,它将英语翻译成西班牙语。我想让这个函数调用谷歌云翻译。服务帐户旨在做一件事。服务帐户是以iam.gserviceaccount.com结尾的电子邮件地址。

因为服务帐户会执行某些操作,所以您会为服务帐户分配一个角色。在这种情况下,我的服务帐户调用Google Cloud Translate,因此我为其分配角色Cloud Translation API User

因为服务帐户只做一件事

,所以您将有限角色分配给服务帐户。例如,我的翻译服务帐户无法在Google Cloud Dataplex中执行任务。它也不一定是Cloud Translation API EditorAdmin。这只是一个Cloud Translation API User的爪牙。

创建服务帐户

在谷歌云控制台的IAM & Admin下,在左侧边栏中查找Service Accounts。在顶部栏中查找+ CREATE SERVICE ACCOUNT

您可以随意命名您的服务帐户。我建议给它取一个Firebase云函数的名字。这清楚地表明了服务帐户的用途。为了更加清楚,在Service account description中写一些类似This service account hooks up the Firebase Cloud Function SuperTranslator to Google Cloud Translate.的内容。在未来的某个时候,你会有太多的服务帐户,你会想要删除一些。

单击DONE

为服务帐户分配角色

现在单击左侧边栏中IAM & Admin下的IAM。您的服务帐户应该在那里,因为服务帐户是IAM的一种特殊类型。

在右侧单击与您的服务帐户关联的铅笔。在Assign roles下,滚动数百个选项,找到您希望服务帐户使用的谷歌云产品或服务。选择产品或服务后,选择一个角色。

正如我前面提到的,分配最低级别的角色,使您的职能发挥作用。在我的翻译函数中,有四个选项:AdminEditorUserReader。级别较低的Reader小黄人只能阅读政策和组织说明。User小黄人可以访问Google云服务。这就是我希望我的服务帐户所做的。Editor可以编辑策略和组织描述,Admin可以完全访问策略和组织说明。我不希望这个服务帐户做那种事。

回到您的IAM页面,您会注意到右侧的每个IAM都有一个超额权限计数(蓝色)。这可能是一个只被要求执行User级别的任务的Admin角色。单击蓝色三角形有时会建议您采取一些措施来减少多余的权限。换言之,IAM的目的是赋予每个爪牙足够的权力来完成自己的工作,而不是更多。

下载您的服务帐户JSON文件

现在,您将把凭据下载到计算机上。在谷歌云控制台中,转到IAM and admin,然后转到Service accounts。单击您想要的服务帐户。

选择KEYS选项卡。单击ADD KEYS按钮并选择Create new key。选择JSON单选按钮。一个模式窗口将打开,询问您将密钥下载到哪里。我有一个用于我的服务帐户密钥的文件夹:

ProjectDirectory/environments/service_account_keys

如果你打开密钥文件,你会看到:

{
"type": "service_account",
"project_id": "my-project",
"private_key_id": "12345abcde",
"private_key": "-----BEGIN PRIVATE KEY-----n1673randomcharacters...=n-----END PRIVATE KEY-----n",
"client_email": "google-cloud-service@my-project.iam.gserviceaccount.com",
"client_id": "12345",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/google-cloud-service%40my-project.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

不要尝试获取现有密钥的服务帐户密钥JSON。就我所见,这是不可能的。如果您需要下载服务帐户密钥,请制作一个新密钥。它们是免费的。

将您的云功能连接到您的服务帐户密钥

这是疯狂的,没有记录的部分。谷歌云支持人员甚至不知道这一点。

要将您的云功能连接到您的服务帐户密钥以在Firebase Emulators中运行,请创建一个TranslationServiceClient(options)对象:

const options = {
keyFilename: '/Users/TDK/VisualStudioCodeProjects/MyProjectDirectory/environments/service_account_keys/my-project-12345.json',
};
import { TranslationServiceClient } from '@google-cloud/translate';
const translationClient = new TranslationServiceClient(options);

您正在将服务帐户密钥的路径作为TranslationServiceClient(options)的参数进行传递。您的options对象只有一个属性keyFilename。属性的值是指向服务帐户密钥的路径。我更喜欢绝对路径,但如果你喜欢,可以使用相对路径。

这是TranslationServiceClient类型的文档。您可以看到还有更多的属性。看起来您可以使用credentials属性将服务帐户电子邮件及其私钥放入您的云功能中。这将是一个坏主意,因为你可能会无意中将你的云功能上传到一个不安全的存储库,并向公众公开你的私钥。

要将您的云功能连接到您的服务帐户密钥以在Firebase Cloud中运行,请使GOOGLE_APPLICATION_CREDENTIALS常量:

import { TranslationServiceClient } from '@google-cloud/translate';
const translationClient = new TranslationServiceClient();
export const GOOGLE_APPLICATION_CREDENTIALS = '/Users/TDK/VisualStudioCodeProjects/MyProjectDirectory/environments/service_account_keys/my-project-12345.json';

不要问我花了多长时间才发现Firebase模拟器和云需要不同的代码。

使用服务帐户部署您的云功能

通常我们会像这样部署Firebase云功能:

firebase deploy --only functions:myAwesomeFunction

还有另一种部署云功能的方法,它允许您连接服务帐户:

gcloud functions deploy myAwesomeFunction --service-account my-awesome-function@my-awesome-project.iam.gserviceaccount.com

如果您使用gcloud和服务帐户部署云功能,则不需要在代码中挂接凭据密钥文件。这很容易,而且看起来安全性更高。

然而。。。上次尝试这样做时,我遇到一个错误,说它找不到lib/index.js文件。这是在functions/package.jsonmain属性中指定的文件。在部署云函数之前运行npm run build时,TypeScript会将代码转换为JavaScript,从src/index.ts转换为lib/index.jsgcloud以前可以找到JavaScript文件,但现在找不到了。

而Firebase团队从未实现过这一点。你不能做:

firebase deploy --only functions:myAwesomeFunction --service-account my-awesome-function@my-awesome-project.iam.gserviceaccount.com

显然,这不建议用于Firebase Cloud函数。

我不认为这是这个答案中引用的文档。因此,这可能不是确保服务帐户访问安全的最佳方式。

测试您的功能

调用您的函数并查看日志,您应该会看到您的Firebase Cloud函数正在执行。

在模拟器中调用函数之前,请记住使用npm run build转换TypeScript。

使用服务帐户修复CORS错误

有时在调用Cloud函数时会出现CORS错误。您可以使用服务帐户来修复CORS错误。选择您的项目服务帐户(my-project@appspot.gserviceaccount.com),制作一个新密钥,并将其保存到您的environments中。取apiKey属性,将其作为defineSecret()的参数传递,然后使用apiKey:调用Cloud函数

import { defineSecret } from "firebase-functions/params";
const apiKey = defineSecret("12345");
export const CallUppercase = onCall({ secrets: [apiKey] }, (request) => {
...
});

通常,这些不是CORS错误,而是其他错误,例如您没有将云函数部署到云中,或者在模拟器中调用云函数之前没有对其进行转换。

工作负载标识联合

当你制作服务帐户密钥时,你看到一个盒子,上面写着

如果服务帐户密钥被泄露,可能会带来安全风险。我们建议您避免下载服务帐户密钥,而是使用Workload Identity Federation。你可以了解更多在谷歌云上验证服务帐户的最佳方式在这里

后一个链接指向博客文章选择在谷歌云上使用和验证服务帐户的最佳方式。这篇博客文章讨论了四个用例:

  1. 将服务帐户附加到部署在Google Cloud 上的云功能、云运行和其他应用程序

  2. 将服务帐户附加到Kubernetes pods

  3. 将服务帐户附加到未在Google Cloud 上运行的应用程序

  4. 正在下载服务帐户密钥

我们刚刚做了后者,只有在前一个选项不可用时才建议使用。第一个选择似乎是谷歌推荐的。Workload身份联合会文档讨论了亚马逊AWS、微软Azure和其他云计算平台,但没有讨论谷歌云功能。安全云功能文档是关于授权谁可以调用您的云功能,而不是授权您的云函数调用其他谷歌云服务。我找不到比下载的服务帐户密钥更好的连接云服务的云功能的文档。

我已经成功地复制了您的示例代码。正如@DazWilkin所提到的,您需要将您的服务帐户绑定到包含cloudtranslate.generalModels.predict的角色。

  1. 转到GCP IAM
  2. 单击GRANT ACCESS
  3. New principals下输入您的服务帐户
  4. 单击Select a role,然后根据IAM权限参考链接上的cloudtranslate.generalModels.predict权限选择云翻译API管理员(roles/cloudtranslate.admin)或云翻译API编辑器(roles/cloudtranslate.editor)或云转换API用户(roles/cloudtranslate.user)
  5. 重新部署您的项目

最新更新