[英]Why am I getting undefined when loading env variables into Firebase function?
I am trying to use the integration between Google Secrets Manager API and Firebase Functions to load environment variables into my Firebase functions, however they are all coming up as undefined.我正在尝试使用 Google Secrets Manager API 和 Firebase 函数之间的集成将环境变量加载到我的 Firebase 函数中,但是它们都未定义。 I was previously using.env.
我之前使用的是 .env。 files to load these variables, which worked fine before I tried this, but now also isn't working.
文件来加载这些变量,在我尝试这个之前工作正常,但现在也不起作用。 I'm using Node.js.
我正在使用 Node.js。
To set secrets on the Secrets API, I ran:为了在 Secrets API 上设置秘密,我运行了:
firebase functions:secrets:set MY_SECRET
I verified the secrets had been set successfully by running the following on each one:我通过在每个密码上运行以下命令来验证密码是否已成功设置:
firebase functions:secrets:access MY_SECRET
I'm defining my functions in index.ts
as follows:我在
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)
And in code, I'm accessing them with process.env.MY_SECRET
.在代码中,我使用
process.env.MY_SECRET
访问它们。 However, when I run firebase serve
(to run in the Firebase emulator) or firebase deploy
, I always get this error followed by a stack trace resulting from the env variable being undefined
:但是,当我运行
firebase serve
(在 Firebase 模拟器中运行)或firebase deploy
时,我总是会收到此错误,然后是由于 env 变量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)
admin.ts:管理员.ts:
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 })
...
(I'm base64 decoding one of the secrets and get an error because it's undefined) (我是 base64 解码其中一个秘密并收到错误,因为它未定义)
package.json: 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
}
I've tried modifying the code so I don't run into errors right away, but this just means my endpoints error later on because the env variable is undefined
.我已经尝试修改代码,所以我不会立即遇到错误,但这只是意味着我的端点稍后会出错,因为 env 变量是
undefined
。
What is going wrong?出了什么问题?
What you're doing will result in undefined
due to incorrect ways of accessing secrets
.你所做的将导致
undefined
due to incorrect ways of accessing secrets
。 In this code snippet:在此代码段中:
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)
You're adding the secret
to the env
variables which then can only be used on the .https.onRequest(apiApp)
.您将
secret
添加到env
变量,然后只能在.https.onRequest(apiApp)
上使用。 Eg例如
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);
The above code will log the SERVICE_ACCOUNT
secret on the function which you passed to.上面的代码将在您传递给的 function 上记录
SERVICE_ACCOUNT
密码。 It's also stated in this documentation :它也在本文档中说明:
Only functions that specifically include a secret in their
runWith
parameter will have access to that secret as an environment variable.只有在其
runWith
参数中明确包含秘密的函数才能访问该秘密作为环境变量。 This helps you make sure that secret values are only available where they're needed, reducing the risk of accidentally leaking a secret.这有助于您确保秘密值仅在需要时可用,从而降低意外泄露秘密的风险。
For you to be able to access your secret without using the .runWith
parameter of the https functions, you must first install the @google-cloud/secret-manager :为了能够在不使用 https 函数的
.runWith
参数的情况下访问您的秘密,您必须首先安装@google-cloud/secret-manager :
npm i @google-cloud/secret-manager
then initiate it:然后启动它:
import {SecretManagerServiceClient} from '@google-cloud/secret-manager';
const client = new SecretManagerServiceClient();
Accessing your secret versions:访问您的秘密版本:
/**
* 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();
For reference, here's the compiled code based on your admin.ts
:作为参考,这是基于您的
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;
}
};
You can then import admin.ts
and call db
with these method.然后您可以导入
admin.ts
并使用这些方法调用db
。
For more information, check out these documentations:有关更多信息,请查看以下文档:
You may also want to checkout Secret Manager Best Practices .您可能还想查看Secret Manager 最佳实践。
I ran into this issue because my imports were leading to admin.initialiseApp being called in index.ts.我遇到这个问题是因为我的导入导致在 index.ts 中调用了 admin.initialiseApp。
index.ts:指数.ts:
import apiApp from "./api/api"
...
api.ts was importing admin.ts through a number of other files, and admin.ts required process.env.SERVICE_ACCOUNT to be populated. api.ts 正在通过许多其他文件导入 admin.ts,而 admin.ts 需要填充 process.env.SERVICE_ACCOUNT。 As Marc Anthony B said, SERVICE_ACCOUNT wouldn't yet be populated if calling from index.ts, hence the error.
正如 Marc Anthony B 所说,如果从 index.ts 调用,SERVICE_ACCOUNT 将不会被填充,因此会出现错误。
I solved by refactoring admin.ts to the following:我通过将 admin.ts 重构为以下内容来解决:
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;
So all my exports were functions, instead of instances of db
and auth
.所以我所有的导出都是函数,而不是
db
和auth
的实例。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.