Search code examples
firebasegoogle-cloud-platformfirebase-hostinggoogle-cloud-buildgoogle-iam

How can I authorize Cloud Build to deploy to Firebase Hosting in a different project?


I have GCP project A and Firebase project B (in a separate GCP project). I'm trying to use Cloud Build in A to build a web app and deploy it to Firebase Hosting in B.

In B's IAM page, I have granted A's <id>@cloudbuild.gserviceaccount.com service account the API Keys Admin, Firebase Admin, and Service Account User roles as described in e.g. this question.

The final step in the Cloud Build config used by A is the following:

  - id: firebase_deploy
    name: gcr.io/$PROJECT_ID/firebase
    entrypoint: sh
    args:
      - '-c'
      - |
        firebase use $_FIREBASE_PROJECT_ID
        firebase target:apply hosting prod $_FIREBASE_HOSTING_TARGET
        firebase deploy --project=$_FIREBASE_PROJECT_ID --only=hosting,firestore:rules

I set the _FIREBASE_PROJECT_ID substitution variable to B and the _FIREBASE_HOSTING_TARGET variable to a Hosting alias that I use for the site.

When I trigger a build, it fails with the following error:

...
Step #3 - "firebase_deploy": Error: Invalid project selection, please verify project B exists and you have access.
Step #3 - "firebase_deploy":
Step #3 - "firebase_deploy": Error: Must have an active project to set deploy targets. Try firebase use --add
Step #3 - "firebase_deploy":
Step #3 - "firebase_deploy": Error: Failed to get Firebase project B. Please make sure the project exists and your account has permission to access it.
Finished Step #3 - "firebase_deploy"

I suspect that the problem may be that I'm not running the Firebase CLI's extra login step first. To do that, it seems that I would need to run firebase login:ci locally to generate a token and then pass it via the FIREBASE_TOKEN environment variable as described in the docs, but the permissions associated with the token appear to be much broader than needed:

screenshot from token generation page

The build process should only have access to Firebase project B, rather than "all my Firebase data and settings" and "my Google Cloud data".

  1. Is there any way to avoid needing to run firebase login here? It seems like the service account should already have sufficient access to deploy to Firebase Hosting.
  2. If I need to run firebase login, is there any way to create a token with a limited scope (assuming that my understanding of the default scope is correct)?

(I've also given B's service account the Cloud Functions Developer role in A and am able to successfully run gcloud --project=$_FIREBASE_PROJECT_ID functions deploy ... in a different build config. I'm also using a Cloud Build config similar to the one described above to deploy to Firebase Hosting in the same GCP project, so I suspect that firebase login isn't necessary in all cases.)


Solution

  • I found an approach that lets project A's Cloud Build service account deploy to B without needing excessive permissions.

    First, I created a service account named deploy under B and granted it the Firebase Hosting Admin, Firebase Rules Admin, and Cloud Datastore Index Admin roles. (I'm not sure whether the Datastore role is needed, but the console showed it as being used recently so I left it.)

    Next, I generated a JSON key for the new service account, pasted it (including newlines and double-quotes) as a substitution variable named _DEPLOY_CREDENTIALS, and updated the build step to copy it to the environment:

     - id: firebase_deploy
        name: gcr.io/$PROJECT_ID/firebase
        entrypoint: bash
        args: ['-e', '--', 'build/deploy_hosting.sh']
        env:
          - DEPLOY_CREDENTIALS=$_DEPLOY_CREDENTIALS
          - FIREBASE_PROJECT_ID=$_FIREBASE_PROJECT_ID
          - FIREBASE_HOSTING_TARGET=$_FIREBASE_HOSTING_TARGET
    

    In deploy_hosting.sh, I write the credentials to a temporary file and then pass them to the firebase command via the GOOGLE_APPLICATION_CREDENTIALS environment variable:

    #!/bin/bash
    
    set -e
        
    CREDS=$(mktemp -t creds.json.XXXXXXXXXX)
    printenv DEPLOY_CREDENTIALS >"$CREDS"
    export GOOGLE_APPLICATION_CREDENTIALS=$CREDS
          
    firebase --debug use "$FIREBASE_PROJECT_ID"
    firebase target:apply hosting prod "$FIREBASE_HOSTING_TARGET"
    firebase deploy --project="$FIREBASE_PROJECT_ID" --only=hosting,firestore:rules
    

    I created a separate shell script for the step since I ran into problems with quotes being stripped from the credentials when writing them directly from the build step. It would likely be possible to store the credentials in Secret Manager instead, but that felt like overkill for my use case.

    I'm still curious about whether there's a way to let A's service account deploy to B without using a service account in B while running the firebase executable.