Store secrets used by Firebase Cloud Functions
Let’s say you configured your backend with Firebase and want to use a third-party service (e.g., Algolia to improve the search of Firestore). That third-party service would issue a private API key for you to safely use that service. It is always better convention to store that kind of secret information on the server rather than on the client side to avoid compromise.
Is there a proper way to do it for Firebase? Yes, of course!! But with some caveat… Let me explain the official way and my personal findings.
Meet Google Cloud Secret Manager
Firebase is part of the Google Cloud Platform (GCP) and it offers a feature called Secret Manager. That is meant for you to store secret keys safely.
If you configured the backend with Firebase, most likely you want to access the third-party service from Cloud Functions. For example, in order to create/update/delete the search index in Algolia, you can hook trigger from Firestore database change and create/update/delete the index of the corresponding data. In this case, you will need to retrieve Algolia’s API key stored in Secret Manager from Cloud Functions.
This type of use case is explained quite well in Firebase's official document. Here, I will explain the gist of it:
First, you need to go to Secret Manager in Google Cloud Console and enable that feature. Make sure the selected project is the same as your Firebase project.
Let’s say you wanted to create a secret key named SAMPLE_SECRET
in a Firebase project with id= algolia-sample
.
% firebase functions:secrets:set SAMPLE_SECRET --project algolia-sample
This shell command will prompt value entry. After you enter the secret value, it will save your secret in Secret Manager in the form that Cloud Functions have access to it.
Then, Cloud Functions can access the secret like this:
export const onChangeDoc = functions
.runWith({secrets: ["SAMPLE_SECRET"]})
.firestore
.document("$path_to_the_document")
.onCreate(async (snap, context) => {
const secret = process.env.SAMPLE_SECRET;
// ... use secret and access third party service ...
});
Easy peasy, right?
Meet some pitfalls
However, in the past, I met some weird cases that the above step didn’t quite work. One of the purposes of this article is to document those cases.
You need to have a Secret Manager Admin role to create secrets
Above firebase functions:secrets:set …
command will fail if you don’t have sufficient permission.
If you are the owner of the Firebase project, everything should be fine since it includes the Secret Manager Admin role as well, but Editor role does not include that.
EDIT: Admin role is also needed to deploy the Cloud Function for the first time after creating/updating secret. You need to be able to add permission to the Firebase service account to access the new secret.
firebase-managed
should be true
There are different ways to create Secret Manager secrets, but if you generate them from firebase
command properly as above, you should see “firebase-managed: true” tag in the Secret Manager section of Google Cloud Console.
If you don’t see that, something went wrong. Try the firebase
command again,
% firebase functions:secrets:set SAMPLE_SECRET --project algolia-sample
it will probably ask you whether you want Firebase to manage the key. You should type “y” and fingers crossed, firebase-managed
should turn true
now.
$firebase_project@appspot.gserviceaccount.com should have a Secret Manager Secret Accessor role
If you go to the IAM settings of your Firebase project, you should see a user named $firebase_project@appspot.gserviceaccount.com
(replace $firebase_project
part with your own project id). This is the service account that runs the Cloud Function. So, in order for the Cloud Function to access the secret, it needs to have a Secret Manager Secret Accessor role as below.
This should be usually automatically added, but sometimes it is not. You may need to grant the role manually yourself.
Great, one purpose of this article is already covered 😄
Access the stored secrets from your local script
In the Algolia’s case, keeping up with Firestore’s record change and updating the search index accordingly is definitely important, but we also want to first run the one-time script to generate all the search indexes from the current snapshot of Firestore records. In order to do that, you want to access the secret from your local script.
For this, you can use Google’s App Default Credential (ADC).
Install gcloud
CLI and configure it. Then,
% gcloud auth application-default login
The browser will launch and will let you log in to Google. Use the same Google account you use for your Firebase project. This will save the credential under your home directory so that other apps you execute can make use of it.
First, let’s install Secret Manager SDK. (This is a node example, but other SDKs should work similarly)
% npm install @google-cloud/secret-manager
And write some local script like this:
const SecretManagerServiceClient = require ('@google-cloud/secret-manager');
const client = new SecretManagerServiceClient();
const main = async function () {
const request = {
// Replace algolia-sample with your Firebase project
// Replace SAMPLE_API_KEY with your secret name in Secret Manager
name: "projects/algolia-sample/secrets/SAMPLE_API_KEY/versions/latest"
};
const response = await client.accessSecretVersion(request):
const secret = response[0].payload.data.toString ('utf8') ;
// ...
// use secret and access third party service
// ...
}
main();
That’s it!! You can access the third-party service using that secret.
Let’s not forget to revoke the saved credential to avoid future security risks after you’re done.
% gcloud auth application-default revoke
A cautionary note is that the Google Account you log in from gcloud auth application-default login
should have Secret Manager Admin or Secret Accessor role. Otherwise, your script still cannot access Secret Manager secrets.
Conclusion
Recently, I had a little troubleshooting to do around this Secret Manager stuff and wanted to summarize my experience. I hope this article might help some of you from wasting your valuable time on troubleshooting like me ;)
Happy coding!!