1
votes

I've got the following code for a an API endpoint that is supposed to trigger a Firestore backup using firebase-admin.

This is how I'm initializing firebase-admin;

import * as admin from "firebase-admin";

admin.initializeApp({
  credential: admin.credential.cert(
    SERVICE_ACCOUNT as admin.ServiceAccount
)});

The service account key is a JSON I've downloaded using the default firebase-admin service account:

enter image description here

This is the backup.ts API request handler.

export const backupData: RequestHandler = async (req, res) => {
  try {

    const PROJECT_ID = process.env.PROJECT_ID;
    
    const client = new admin.firestore.v1.FirestoreAdminClient();
    const DB_NAME = client.databasePath(PROJECT_ID, "(default)");

    const BUCKET = `gs://${PROJECT_ID}.appspot.com`;
    const FOLDER = `firestore-backup`;
    const FULL_PATH = `${BUCKET}/${FOLDER}`;

    const responses = await client.exportDocuments({
      name: DB_NAME,
      outputUriPrefix: FULL_PATH,
      collectionIds: [] // CAN LIST SPECIFIC COLLECTIONS
    });

    const response = responses[0];
    return res.sendStatus(200);
  } else 
    return res.sendStatus(403);
  }
  catch(err) {
    console.error(err.message || DEFAULT_ERROR_MSG);
    return res.sendStatus(500);
  }
};

The weird thing is:

It works perfectly on my dev environment. I mean, I run my local server and hit localhost:8080/api/backup (which is the correct endpoint) and everything works correctly and the backup is triggered.

But after I deploy it to my cloud run service, it no longer works.

This is the error I'm getting on my cloud run service:

7 PERMISSION_DENIED: The caller does not have permission

Do I need to add more permissions to that service account? But why does it work with that very same service account on my local dev environment? Should I not use a service account when I'm initializing it on my cloud run service? Don't know what to do here.


UPDATE 1:

I've just tried to initialize it like this, but got the same results:

admin.initializeApp({
  credential: admin.credential.cert(SERVICE_ACCOUNT),
  databaseURL: `https://${PROJECT_ID}.firebaseio.com`,
  storageBucket: `${PROJECT_ID}.appspot.com`
});

Also, just followed this doc tutorial and added these roles both to my firebase-admin and [email protected]. Still not working:

  • Cloud Datastore Import Export Admin
  • Storage Admin

UPDATE 2:

Following this doc, I've just tried to initialize the firebase-admin without any parameters. Still same result:

admin.initializeApp();

UPDATE 3:

Following the previous docs and this doc, I've added the IAM roles of Cloud Datastore Import Export Admin and Storage Admin to my [email protected] service account. Still same results.


CONCLUSION

It just worked. But I've done so many attempts that I'm not really sure of what made it work. I'm guessing it was what I did on UPDATE 3. Will check that out and answer tomorrow.

1
You seem to be on the correct track. My service account has "storage legacy object owner" on the bucket and the following roles on the service account: storage bucket list, Cloud Datastore Import Export Admin, Cloud Datastore User. A collection-based backup is done daily. I see you have it working. Perhaps at this point setup a new service account to try and get least privileges set. - Brettski
It takes less than a minute up to 7 minutes for IAM permissions to propagate so that could explain things if your 3rd update fixed the issue. - Dondi
@DonnaldCucharo That is probably what happened, indeed. Good to know. Thanks. - cbdeveloper

1 Answers

5
votes

I can confirm that UPDATE 3 that I described on the question did the trick. Because I've reverted the changes I made on UPDATE 1 and 2 and it's still working fine.

It seems that while on my local environment, firebase-admin is using the firebase-adminsdk@PROJECT_ID.iam.gserviceaccount.com service account to call the FirestoreAdminClient API. That's why it works I guess. Or maybe it's using my gcloud logged account which is my owner email. I'm not really sure.

But once you are in Cloud Run environment, even though firebase-admin is being initialized with that very same service account, it seems that it kind of overrides that and uses the [email protected]. So that's the one that needs the permissions.

I got that idea from Cloud Run Service Identity and Cloud Run IAM roles, that says:

enter image description here

And the roles needed I got from: Firebase DOCS - Scheduled exports:

enter image description here

This is the final config:

enter image description here