Call Cloud Run from Cloud Function: IAM Authentication

Viewed 1974

I've deployed a small HTTP endpoint via Google Cloud Run. It is working fine when I turn off the authentication.

I now want to turn it on so that it is only callable by my Firebase Cloud Function. If I understand it right, I just have to add the correct service account mail address in the IAM settings of the Cloud Run as "Cloud Run invoker". But which address is the correct one?

I've tried all addresses that I have found in Firebase Console -> Project Settings -> Service Accounts.

3 Answers

I think you can check the specific firebase function. In the UI, the service account used should be listed.

By default, GCF functions all use <project_id>@appspot.gserviceaccount.com

Thanks to @AhmetB - Google and @whlee's answer I got it working. Basically it is enough adding an Authorization Bearer token to the request, which you can get from a special endpoint: https://cloud.google.com/run/docs/authenticating/service-to-service#nodejs

Then you just have to add the service account of the function to the IAM list of the Cloud Run container: <project_id>@appspot.gserviceaccount.com

The nodejs example is using the deprecated request library, so here is my version using axios:

    const getOAuthToken = async (receivingServiceURL: string): Promise<string> => {

      // Set up metadata server request
      const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
      const uri = metadataServerTokenURL + receivingServiceURL;
      const options = {
        headers: {
          'Metadata-Flavor': 'Google'
        }
      };

      return axios.get(uri, options)
        .then((res) => res.data)
        .catch((error) => Promise.reject(error));
    }

Then you can just use the token in the actual request:

    const url = `...`;
    const token = await getOAuthToken(url);

    axios.post(url, formData, {
        headers: {
            Authorization: `Bearer ${token}`,
        }
    }).then(...).catch(...);

@luhu 's answer was really helpful. I'd like to add just one note for those whose are willing to test with the emulators locally first. The metadata server (which is actually http://metadata.google.internal now) as they state

does not work outside of Google Cloud, including from your local machine.

As a workarund, you can use the google-auth-library and then get the token directly if you prefer sticking with axios. Remember to set the GOOGLE_APPLICATION_CREDENTIALS env variable pointing to a service account secret first as it's the only way to make it work (I've tested setting the credential field during admin.initializeApp() but didn't seem to like it).

const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();

const url_origin = '....'
const client = await auth.getIdTokenClient(url_origin);
const token = (await client.getRequestHeaders()).Authorization;
const url = '....'

const response = await axios.get(
  url,
  {
   headers: {
    Authorization: token,
   },
  }
);
Related