3
votes

I have a cloud function I want to call from a rails app that is currently running on Heroku. I have a service account set up for the rails app, and the could function is deployed and triggered by HTTP request. I want to limit invocations of the cloud function to the rails app, and was planning to use server-to-server 2-legged OAuth 2.0 following this documentation from google.

When I'm setting up the service account though, I need to supply a scope. According to the list of available scopes the only scope referring to Cloud Functions allows management of the cloud function, whereas I want a scope to cover invocation only.

The language used in the docs refers to scopes being used to define access to Google APIs.

  • Does my cloud function qualify as a google API in this sense?
  • Otherwise, since scopes are URLs, could I just supply the URL of my cloud function as a scope?
2
Is it an option for you to use API Key for your call?guillaume blaquiere
@guillaumeblaquiere Yes, it's a possibility. But I want to know if I can get OAuth working as a mechanism of limiting cloud function invocationssirlark
Is the function is private? If so, do you use OAuth for being authenticated? If not, you use the JWT token to authenticate the user by yourself?guillaume blaquiere
The function is private, and the plan is to use OAuthsirlark
If you use OAuth with service account, use the audience equal to your function root URL. And add the role cloudfunctions.invoker to the service account for allowing it to reach the function. More detail hereguillaume blaquiere

2 Answers

4
votes

Right, I managed to cobble the process together from several disparate Google documentation pages, namely

  1. https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_a_service_account
    • incorrectly instructs you to self-sign your JWT, which obviously won't work because then anyone could sign their own JWT and access your cloud function
  2. https://cloud.google.com/functions/docs/securing/authenticating#service-to-function
    • this example doesn't tell you what audiences to set in your initial JWT claim
  3. https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests
    • incorrectly describes the JWT claim contents and signing process
    • the alternate method of using service account signed JWTs directly as bearer tokens doesn't work for calling cloud function, I'm guessing because they aren't google APIs

Here's what worked for me

  1. Marshal a JWT with the following data. The main trick was to set aud to the OAuth endpoint, set sub to the service account's email (even though there's no user to impersonate), and target_audience to the Cloud Function's URL (per doc #1), but follow the general process in doc #3.

    {
      "aud": "https://www.googleapis.com/oauth2/v4/token",
      "sub": "<your service account's email style identifier>",
      "iss": "<your service account's email style identifier>",
      "target_audience": "<the URL of the cloud function you're trying to call>",
      "exp": <unix epoch timestamp + 3600 as an integer>,
      "iat": <unix epoch timestamp as an integer>
    }
    
  2. Base64 encode the claim. Base64 encode a claim header set ({"alg":"RS256","typ":"JWT"}) and combine the results with a period, header.claim, per generic instructions in doc #3. You should get something like this: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjkt.... Contrary to doc #3, you don't need kid in the headers.

  3. Use the service account private key to sign the JWT with your favourite JWT library

  4. Exchange your service account signed JWT for an access token from Google's OAuth system, by making POST call to https://www.googleapis.com/oauth2/v4/token with an Authorization: Bearer <signed JWT base64 encoded string> and the following two fields form data encoded:

    • grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
    • assertion: <signed JWT base64 encoded string, YES you use it twice>
  5. If all goes well, you should receive a JSON response containing a key id_token, which you can use in an Authorization: Bearer <id_token> when you call your CloudFunction

0
votes

The Cloud Functions documentation regarding this topic covers several use cases:

  • Securing developer access so that only specific users can invoke the function during testing.

  • Securing function-to-function access so that only authorized functions can invoke your function.

  • Securing end-user access to an application from mobile or web clients.

Your use case is the third one, the end-user authentication is integrated through Google Sign-In and Firebase Authentication but this won't be useful for restricting HTTP calls to allow only your Heroku App.

At this point the workaround should be managed by checking in your Cloud Functions code whether the request comes from the Heroku App and reject those which doesn't.