0

My architecture is:

  1. I have bunch of cloud functions (firebase)
  2. I have setup a ESPv2 based cloud endpoints setup with cloud run with the cloud functions as backend
  3. I have a SPA running on firebase hosting which is making calls to the endpoints (15 api endpoints)
  4. For now the APIs (cloud endpoints) are secured via APIKey (restricted)

I want make the cloud functions secured such that only the cloud endpoints (or other services within my project) can make calls to it. Directly calling the fqdns of these cloud functions should yield 403/4xx.

How do I achieve this? Is there any way?

** I don’t have organisation account so creating a service perimeter isn’t an option.

EDIT

I have accepted guillaume blaquiere's ans as the correct one. Although the ans the did not solve the issue I was facing but it is the correct ans to the question I asked.

In case anyone facing similar issue, my biggest mistake (and I wasted 1 day behind it) was with 'x-google-backend > address'. I was using only the FQDN of the cloud function not the entire URL. So This is wrong:

/myfunction/mymodule1/dosomething:
    post:
      summary: I command you to do something
      operationId: doSomething
      x-google-backend:
        address: https://xxxxxxxx.cloudfunctions.net
        path_translation: APPEND_PATH_TO_ADDRESS
      parameters:
        - name: someheaderparam
          in: header
          description: shhh
          required: true
          type: string
        - name: payload
          in: body
          description: playload
          required: false
          schema:
            type: object
      responses:
        '200':
          description: OK
          schema:
            type: object
        '401':
           $ref: '#/responses/UnauthorizedError'

For this to work I needed to use the entire url of the function not just FQDN.

I my case my firebase/cloud function was an express app where I had published the function like this

exports.myfunction = functions.https.onRequest(app);

So the name of the cloudfunction is myfunction and its base url is:

https://xxxxxxxx.cloudfunctions.net/myfunction

Using express I added several paths to it, such as

app.post('/mymodule1/dosomething', validateToken(), somemethod);

If my function were to be publicly accessible, in order to invoke 'somemethod' function the url would have been:

https://xxxxxxxx.cloudfunctions.net/myfunction/mymodule1/dosomething

In order to have this mapped using openAPI behind api gateway the specification looks like this:

/mymodule1/dosomething:
    post:
      summary: I command you to do something
      operationId: doSomething
      x-google-backend:
        address: https://xxxxxxxx.cloudfunctions.net/myfunction
        path_translation: APPEND_PATH_TO_ADDRESS
      parameters:
        - name: someheaderparam
          in: header
          description: shhh
          required: true
          type: string
        - name: payload
          in: body
          description: playload
          required: false
          schema:
            type: object
      responses:
        '200':
          description: OK
          schema:
            type: object
        '401':
           $ref: '#/responses/UnauthorizedError'

This is also true even if the function is not an express app. In the openAPI specification, the x-google-backend address must be the URL of the function not just the FQDN of the cloud function.

Ali Nahid
  • 867
  • 11
  • 26

1 Answers1

2

To achieve this, you need 2 things

  • Firstly to restrict access to your Cloud Function
  • Then, you need to all only allow Cloud Endpoints to access to your functions

Like this, the function will be reachable from the internet but only the authorized service (ESPv2) will be able to invoke them.


  1. For the first point, deploy your function in private mode (add --no-allow-unauthenticated param when you deploy them)

  2. Then,

  • Create a service account dedicated to your ESPv2 service (if not yet the case)
  • Redeploy your ESPv2 service with this custom service account
  • Grant the service account the roles/cloudfunctions.invoker
    • Either on the project, this means the service account will be able to invoke any function in the project
    • Or at the functions level to allow the service account to invoke only a subset of functions. It's particularly interesting when all the functions aren't in the same project.
guillaume blaquiere
  • 66,369
  • 2
  • 47
  • 76
  • thanks for the suggestion. I removed "allUsers" membership from one of the cloud functions. ESPv2 is already deployed with a specific service account "apigatewwaysa@xxx.iam.gserviceaccount.com" which has cloudfunctions.invoker role. But this is still not working. I get 401 "

    Error: Unauthorized

    Your client does not have permission to the requested URL

    " when I call "https://xxx-xxxx-uc.a.run.app/api/myapiname". I get 403 "Error: Forbidden" when I call cloud function url directly "https://xxxx-xxxx.cloudfunctions.net/api/myapiname". What else can I try?
    – Ali Nahid Nov 24 '20 at 21:27
  • I [wrote an article](https://medium.com/google-cloud/secure-cloud-run-cloud-functions-and-app-engine-with-api-key-73c57bededd1) on this. And it should work like this! Check the authorization and the roles. It should work!! – guillaume blaquiere Nov 24 '20 at 21:38
  • I followed your tutorial to set it up. I re-read it. Still no luck. The only differences between your tutorial and my implementation are: I am deploying Firebase cloud functions then removing allUsers from permission (to make it --no-allow-unauthenticated) and I am using ESPv2 found here: https://cloud.google.com/endpoints/docs/openapi/get-started-cloud-run I can confirm that the endpoint can call a -no-allow-unauthenticated cloud run (with cloud run invoker role), no issues. It's only the Firebase cloud functions that causing issue (even with cloud functions invoker role to sa). – Ali Nahid Nov 24 '20 at 23:50
  • I also looked at this: https://stackoverflow.com/questions/46358013/secure-google-cloud-functions-http-trigger-with-auth (see the first answer). My setup is same except instead of cloud scheduler I am invoking it from cloud endpoints. One thing I am not sure though is the "allow OIDC header" part. How does that work? For cloud run/cloud endpoint does the ESPv2 need to allow Authorization header? – Ali Nahid Nov 24 '20 at 23:55
  • No reason except if the Cloud Functions isn't in the same project as the ESPv2. Do you have checked this? Do you see your function in the project? – guillaume blaquiere Nov 25 '20 at 09:45
  • Thanks for your ans. Turns out I was adding the cloud functions as backend wrongly. Your ans is correct to the question asked. – Ali Nahid Dec 20 '20 at 19:59