3

After reading every piece of question, documentation available online, we couldn't find a solution, so posting a question here.

Our Setup :

  1. Project FB: Used only for Firestore in Dev mode. We want to access data from this project in our dev backend server, hosted on a different cloud project.

  2. Project GCP: A GCP Project with app deployed in GAE that makes a simple get() call to Project FB's document. The app is in Python, and it works perfectly in localhost, but not after it's deployed.

Sample Flask code that we are testing with:

route('/test-fb', methods=['POST', 'GET'])
def test():
    doc = doc_ref.get()
    if doc.exists:
        print(u'Document data: {}'.format(doc.to_dict()))
    else:
        print(u'No such document!')
    return make_response("Firestore worked!")

Note:

  1. This is not a question on Firestore Rules, because we are using service account key. To be 100% sure in any case, we have rules set to: true always

    match /{document=**} { allow read, write: if true

  2. We generate the Service Account private key by going to "Settings -> Service Accounts", generate new private key. Then use the Admin SDK config snippet code in Python above our code. This works perfectly in localhost.

  3. Aware of the Service Account permissions needed, we added many permissions including 'Editor', 'Storage Admin' and 'Cloud Datastore Owner' to the Project FB IAM account for GAE service Account of Project GCP (@appspot.gserviceaccount.com)

  4. All the Firestore packages, any other dependency are updated to the latest version.

  5. Created new keys to test again. For Project FB, updated Credentials -> Key restrictions and set them to unrestricted so any domains can access them.

  6. Deleted the versions, and tried again many times at different times of the day as well. The deployments happen through triggered cloud builds in Project GCP. The cloud builds are successful. Also, all routes function perfectly except the one in which we are reading the Firestore document (code above).

  7. Deleted cookies, and tried different browsers.

  8. Instead of using the snippet code, also tried the google-cloud-firestore package: https://pypi.org/project/google-cloud-firestore/

  9. Both the projects are in the same location (US multilocation)

Please advise on what we could be doing wrong, and what else we can try? We are lost at this point, and this simple task has taken us several days, and we've tried all permutations of above steps multiple times to double check.

GAE Response on a request to the server: enter image description here enter image description here

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
sudcha
  • 623
  • 6
  • 18
  • If Step Three (Service Account Permissions) is setup correctly, then maybe the issue is in Step Two (the Admin SDK config snippet). For localhost testing, are you pointing to a specific key file like this, `cred = credentials.Certificate('path/to/serviceAccount.json')`? When you deploy to GAE, are you using something like `cred = credentials.ApplicationDefault()`? – Juan Lara May 26 '20 at 20:20
  • For localhost and GAE deployment, we tested with two approaches. One is setting up `os.environ('GOOGLE_APPLICATIONS_CREDENTIAL") = "firebase-server-admin.json"` and the other directly assigning the key using something like: `cred = credentials.Certificate("firebase-server-admin.json")`. In both the cases, calls work perfectly in localhost, but not GAE. The paths are relative, so we are sure they are correct (also tested by printing os.environ variable in Logs to be sure). – sudcha May 26 '20 at 22:19
  • 1
    I wonder if that is pointing to the right location locally, but not in the GAE environment. Have you tried deploying to GAE without setting `GOOGLE_APPLICATIONS_CREDENTIALS` at all? GAE should set this environment variable automatically. Or with `cred = credentials.ApplicationDefault()` [like in this example](https://firebase.google.com/docs/firestore/quickstart#initialize). – Juan Lara May 27 '20 at 00:06
  • ^ This was the key to resolving our issue. Thanks for sharing minimalistic code! – sudcha May 27 '20 at 12:35

1 Answers1

9

I tried this with a minimal example and it worked. Make sure your GAE app is using the application default credentials.

Deploying a Flask app in project-foo with access to a Firestore DB in project-bar:

main.py

from flask import Flask
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore

app = Flask(__name__)

# Use the application default credentials
cred = credentials.ApplicationDefault()
firebase_admin.initialize_app(cred, {
  'projectId': 'project-bar',
})

db = firestore.client()

@app.route('/test-fb', methods=['POST', 'GET'])
def test():
    doc = db.collection('users').document('123').get()
    if doc.exists:
        print(u'Document data: {}'.format(doc.to_dict()))
    else:
        print(u'No such document!')
    return 'Firestore worked!'

if __name__ == '__main__':

Deploy app in project-foo

gcloud set project project-foo
gcloud app deploy

Visit project-foo.uc.r.appspot.com/test-fb. As expected, see permission denied error in logs.

Give project-foo's default service account access to project-bar's Firestore DB

gcloud set project project-bar
gcloud projects add-iam-policy-binding project-bar \
  --member serviceAccount:project-foo@appspot.gserviceaccount.com \
  --role roles/datastore.user

Wait a few minutes for IAM binding to take hold, refresh project-foo.uc.r.appspot.com/test-fb. See Firestore worked!.

Juan Lara
  • 6,454
  • 1
  • 22
  • 31
  • Thank you so much! We definitely missed the code link to Firebase documentation you shared above, and never really gave deeper thought to GOOGLE_APPLICATIONS_CREDENTIALS in GCP. Neither of the approaches we tried used credentials.ApplicationDefault(). Also, as you suggested, roles/datastore.user worked for us, but roles/datastore.admin did not (didn't validate). Thanks again :) – sudcha May 27 '20 at 12:33
  • Thanks a lot.. I had spent 2 days trying to get this working – Kevin Cohen Apr 06 '21 at 20:32
  • Thank you for the `--role roles/datastore.user` , i want to give you a bounty! – Ivan Chernykh Oct 24 '21 at 13:29
  • Wow. This is... obscure and it certainly fixed the problem for me. The "set" command doesn't seem to work anymore but I didn't have to do that. I just did the gloud projects... part. – screenglow Jan 07 '22 at 03:30