为什么在将env变量加载到Firebase函数中时会出现未定义的情况



我正在尝试使用Google Secrets Manager API和Firebase函数之间的集成来将环境变量加载到我的Firebase函数中,但它们都是未定义的。我以前使用.env.files加载这些变量,在尝试之前效果很好,但现在也不起作用!我正在使用Node.js.

为了在秘密API上设置秘密,我运行:

firebase functions:secrets:set MY_SECRET

我通过在每一个上运行以下程序来验证秘密是否已成功设置:

firebase functions:secrets:access MY_SECRET

我在index.ts中定义我的函数如下:

import * as functions from 'firebase-functions'
import apiApp from "./api/api"
const REGION = "my region as a string"
const secrets = ["SERVICE_ACCOUNT"]
export const api = functions
.region(REGION)
.runWith({ secrets })
.https.onRequest(apiApp)

在代码中,我用process.env.MY_SECRET访问它们。然而,当我运行firebase serve(在Firebase模拟器中运行(或firebase deploy时,我总是会得到这个错误,然后是由环境变量undefined:产生的堆栈跟踪

Error: Error occurred while parsing your function triggers.
InvalidCharacterError
at /.../functions/node_modules/base-64/base64.js:23:36
at Object.<anonymous> (/.../functions/node_modules/base-64/base64.js:164:2)
at Module._compile (node:internal/modules/cjs/loader:1097:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1151:10)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:999:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/.../functions/lib/admin.js:5:16)
at Module._compile (node:internal/modules/cjs/loader:1097:14)

管理员:

import * as admin from 'firebase-admin'
import * as base64 from 'base-64'
const serviceAccount = JSON.parse(base64.decode(process.env.SERVICE_ACCOUNT))
const credential = admin.credential.cert(serviceAccount)
admin.initializeApp({ credential })
...

(我正在对其中一个秘密进行base64解码,但由于未定义,因此出现错误(

package.json:

{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"build": "tsc",
"serve": "npm run build && firebase emulators:start --only functions",
"shell": "npm run build && firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log",
"postbuild": "copyfiles -u 1 src/**/*.handlebars src/**/*.json lib/"
},
"engines": {
"node": "16"
},
"main": "lib/index.js",
"dependencies": {
...
"base-64": "^1.0.0",
"firebase-admin": "^10.0.2",
"firebase-functions": "^3.18.0",
...
},
"devDependencies": {
"@babel/runtime": "^7.17.2",
"@types/base-64": "^1.0.0",
...
},
"private": true
}

我已经尝试过修改代码,这样我就不会马上遇到错误,但这意味着我的端点稍后会出错,因为env变量是undefined

出了什么问题?

由于访问secrets的方式不正确,您所做的操作将导致undefined。在这个代码片段中:

import * as functions from 'firebase-functions'
import apiApp from "./api/api"
const REGION = "my region as a string"
const secrets = ["SERVICE_ACCOUNT"]
export const api = functions
.region(REGION)
.runWith({ secrets })
.https.onRequest(apiApp)

您将secret添加到env变量中,然后只能在.https.onRequest(apiApp)上使用。例如

app.get('/', (req, res) => {
console.log(process.env.SERVICE_ACCOUNT);
return res.send(`Done!`);
});
const secrets = ["SERVICE_ACCOUNT"];
export const api = functions
.region('us-central1')
.runWith({ secrets })
.https.onRequest(app);

上面的代码将记录您传递给的函数的SERVICE_ACCOUNT秘密

只有在runWith参数中专门包含机密的函数才能将该机密作为环境变量访问。这有助于确保机密值仅在需要的地方可用,从而降低意外泄露机密的风险。


为了能够在不使用https函数的.runWith参数的情况下访问您的机密,您必须首先安装@google cloud/secret manager:

npm i @google-cloud/secret-manager

然后启动它:

import {SecretManagerServiceClient} from '@google-cloud/secret-manager';
const client = new SecretManagerServiceClient();

访问您的秘密版本:

/**
* TODO(developer): Uncomment these variables before running the sample.
*/
// const name = 'projects/my-project/secrets/my-secret/versions/5';
// const name = 'projects/my-project/secrets/my-secret/versions/latest';
async function accessSecretVersion() {
const [version] = await client.accessSecretVersion({
name: name,
});
// Extract the payload as a string.
const payload = version.payload.data.toString();
// WARNING: Do not print the secret in a production environment - this
// snippet is showing how to access the secret material.
console.info(`Payload: ${payload}`);
}
accessSecretVersion();

作为参考,以下是基于admin.ts:编译的代码

import * as admin from 'firebase-admin';
import {SecretManagerServiceClient} from '@google-cloud/secret-manager';
import * as base64 from 'base-64';
const client = new SecretManagerServiceClient();
// Must follow expected format: projects/*/secrets/*/versions/*
// You can always use `latest` if you want to use the latest uploaded version.
const name = 'projects/<PROJECT-ID>/secrets/SERVICE_ACCOUNT/versions/latest'

let credentials: admin.app.App;
export const db = async (): Promise<admin.app.App> => {
if (credentials) {
return credentials;
} else {
const [version] = await client.accessSecretVersion({
name: name
});
const result: any = JSON.parse(version?.payload?.data?.toString());
const params = {
type: result.type,
projectId: result.project_id,
privateKeyId: result.private_key_id,
privateKey: result.private_key,
clientEmail: result.client_email,
clientId: result.client_id,
authUri: result.auth_uri,
tokenUri: result.token_uri,
authProviderX509CertUrl: result.auth_provider_x509_cert_url,
clientC509CertUrl: result.client_x509_cert_url,
};
credentials = admin.initializeApp({
credential: admin.credential.cert(params),
storageBucket: `gs://${result.project_id}.appspot.com`,
});
return credentials;
}
};

然后可以导入admin.ts并使用这些方法调用db

欲了解更多信息,请查看以下文档:

  • 创建和访问机密
  • 管理机密
  • 管理机密版本
  • API参考文档

您可能还想签出密钥管理器最佳实践

我遇到这个问题是因为我的导入导致admin.initializeApp在index.ts.中被调用

index.ts:

import apiApp from "./api/api"
...

api.ts通过许多其他文件导入admin.ts,admin.ts需要填充process.env.SERVICE_ACCOUNT。正如Marc Anthony B所说,如果从index.ts调用,SERVICE_ACCOUNT还不会被填充,因此出现了错误。

我通过重构管理员解决了以下问题:

import * as admin from "firebase-admin";
import * as base64 from "base-64";
let dbInstance: admin.firestore.Firestore | null = null;
let authInstance: admin.auth.Auth | null = null;
function getAdmin() {
const serviceAccount = JSON.parse(base64.decode(process.env.SERVICE_ACCOUNT));
const credential = admin.credential.cert(serviceAccount);
admin.initializeApp({ credential });
dbInstance = admin.firestore();
authInstance = admin.auth();
return { db: dbInstance, auth: authInstance };
}
export const db = () => dbInstance || getAdmin().db;
export const auth = () => authInstance || getAdmin().auth;

所以我所有的导出都是函数,而不是dbauth的实例。

我遇到了一个类似的问题,在尝试使用initializeApp()时,环境变量未定义

@mef27的解决方案也适用于我。但我使用的是Firebase Admin sdk的新版本。

因此,对于2023年读到这篇文章的人来说,这对我很有效:

import { getAuth, Auth } from "firebase-admin/auth";
import { getStorage, Storage } from "firebase-admin/storage";
import { getApp, getApps, initializeApp, cert } from "firebase-admin/app";
let authInstance: Auth | null = null;
let storageInstance: ReturnType<Storage["bucket"]> | null = null;
function getAdmin() {
const app = !getApps().length
? initializeApp({
credential: cert({
projectId: process.env.FB_PROJECT_ID,
clientEmail: process.env.FB_CLIENT_EMAIL,
privateKey: process.env.FB_PRIVATE_KEY,
}),
storageBucket: process.env.FB_STORAGE_BUCKET,
})
: getApp();
authInstance = getAuth(app);
storageInstance = getStorage(app).bucket();
return { auth: authInstance, storage: storageInstance };
}
export const auth = () => authInstance ?? getAdmin().auth;
export const storage = () => storageInstance ?? getAdmin().storage;

相关内容

  • 没有找到相关文章

最新更新