So I have a firebase project that uses firebase functions as a backend service. I am using Google Cloud KMS nodejs client in one of the routes.
When serving the functions locally using firebase serve I get the following error, i have replaced the project number with <project_id_number>
KMS_ERROR Error: 7 PERMISSION_DENIED: Cloud Key Management Service (KMS) API has not been used in project <project_id_number> before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudkms.googleapis.com/overview?project=<project_id_number> then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
A few things that are causing my confusion. The <project_id_number> shown does not exist in the list of projects I own from firebase projects:list
and opening the link shows me a permission error. The function has permission to use KMS though a service account and is working correctly when deploying the function to google cloud. I have set my Application Default Credentials (ADC) via the gcloud cli and can successfully run KMS commands using the KMS client when directly running a node script e.g. node scripts/kms_test.js
.
Do I need to set up anything else for this to work?
It would be pretty tough to try develop functionality if I can't run/test it locally. Any help is appreciated, thanks in advance.
I haven't been able to find anything online relating to this issue. So if there is anything, just point me in the right direction.
Edit: Here is a code snippet that I have tested to be working when deployed to google cloud but doesn't work when running firebase serve. I have put in the relevant pieces. Using express in this case. Doing some more reading, It seems I don't fully understand how firsebase serve works under the hood. So I suspect there requires some additional config to set this up in addition to ADC. Any help is appreciated.
const { KeyManagementServiceClient } = require("@google-cloud/kms");
const crc32c = require("fast-crc32c");
app.post("/some_route", authRouteHandler,
async (req, res) => {
let encode = Boolean(req.body.encode);
let text = req.body.text;
if (!text || typeof text !== "string") {
return res.status(400).json({});
}
const projectId = "<project_Id>";
const locationId = "<location_Id>";
const keyRingId = "<key_ring_id>";
const keyId = "<key_id>";
if (encode) {
try {
let encodedTextBase64 = await encryptText(projectId, locationId, keyRingId, keyId, text);
return res.status(200).json({ encodedTextBase64 });
} catch (err) {
functions.logger.error("KMS_ERROR", err);
return res.status(500).json({})
}
} else {
try {
let decodedText = await decryptText(projectId, locationId, keyRingId, keyId, text);
return res.status(200).json({ decodedText });
} catch (err) {
functions.logger.error("KMS_ERROR", err);
return res.status(500).json({});
}
}
}
);
const encryptText = async (projectId, locationId, keyRingId, keyId, textToEncrypt) => {
//creating the client
const client = new KeyManagementServiceClient();
const keyName = client.cryptoKeyPath(projectId, locationId, keyRingId, keyId);
const plaintextBuffer = Buffer.from(textToEncrypt);
const plaintextCrc32c = crc32c.calculate(plaintextBuffer);
const [encryptResponse] = await client.encrypt({
name: keyName,
plaintext: plaintextBuffer,
plaintextCrc32c: {
value: plaintextCrc32c,
},
});
const ciphertext = encryptResponse.ciphertext;
if (!encryptResponse.verifiedPlaintextCrc32c) {
throw new Error("Encrypt: request corrupted in-transit");
}
if (crc32c.calculate(ciphertext) !== Number(encryptResponse.ciphertextCrc32c.value)) {
throw new Error("Encrypt: response corrupted in-transit");
}
return ciphertext.toString("base64");
};
const decryptText = async (projectId, locationId, keyRingId, keyId, base64Text) => {
//creating the client
const client = new KeyManagementServiceClient();
const keyName = client.cryptoKeyPath(projectId, locationId, keyRingId, keyId);
const ciphertext = Buffer.from(base64Text, "base64");
const ciphertextCrc32c = crc32c.calculate(ciphertext);
const [decryptResponse] = await client.decrypt({
name: keyName,
ciphertext: ciphertext,
ciphertextCrc32c: {
value: ciphertextCrc32c,
},
});
if (crc32c.calculate(decryptResponse.plaintext) !== Number(decryptResponse.plaintextCrc32c.value)) {
throw new Error("Decrypt: response corrupted in-transit");
}
const plaintext = decryptResponse.plaintext.toString();
return plaintext;
};
I ran into the same thing. My issue was that I forgot to set my GOOGLE_APPLICATION_CREDENTIALS
environment variable BEFORE I run firebase serve
. I'm on Mac, so this is done via:
export GOOGLE_APPLICATION_CREDENTIALS="path/to/key.json"
Unless you set this environment variable to your permanent PATH, you'll have to do this every time you start a new terminal session for your Firebase service.
See here: https://firebase.google.com/docs/functions/local-emulator#set_up_admin_credentials_optional