Search code examples
firebasegoogle-cloud-platformgoogle-cloud-functionsgoogle-cloud-kms

Firebase serve, PERMISSION_DENIED, API has not been used in project before or it is disabled


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;
};


Solution

  • 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