2

I've deployed a simple GCP Cloud Function which returns "Hello World!". I need this function to be under authorization. I unmarked "Allow unauthenticated invocations" checkbox, so only authenticated invocations can call this code. I also created Service Account and give next roles: - Cloud Functions Invoker - Cloud Functions Service Agent

my code:

from google.oauth2 import service_account
from google.auth.transport.urllib3 import AuthorizedHttp

if __name__ == '__main__':
    credentials = service_account.Credentials.from_service_account_file('service-account.json',
        scopes=['https://www.googleapis.com/auth/cloud-platform'],
        subject='service-acc@<project_id>.iam.gserviceaccount.com')
    authed_session = AuthorizedHttp(credentials)
    response = authed_session.urlopen('POST', 'https://us-central1-<project_id>.cloudfunctions.net/main')
    print(response.data)

and I've got response:

b'\n<html><head>\n<meta http-equiv="content-type" content="text/html;charset=utf-8">\n<title>401 Unauthorized</title>\n</head>\n<body text=#000000 bgcolor=#ffffff>\n<h1>Error: Unauthorized</h1>\n<h2>Your client does not have permission to the requested URL <code>/main</code>.</h2>\n<h2></h2>\n</body></html>\n'

How to become authorized?

John Hanley
  • 74,467
  • 6
  • 95
  • 159
Voldemort93
  • 23
  • 1
  • 5
  • Where are you trying to invoke the function from? There are a number of options depending on your answer: https://cloud.google.com/functions/docs/securing/authenticating – Dustin Ingram Jan 21 '20 at 17:55
  • Your code is generating and sending an OAuth Access Token. You need to generate and send an Identity Token. – John Hanley Jan 21 '20 at 18:31
  • The error is because the caller is not authenticated, also I see that the code you are deploying is to generate the Access Token. Usually where the access tokens are generated have public access, what do you want to do? – Soni Sol Jan 21 '20 at 22:08
  • Thank you @DustinIngram. I'm trying to invoke it from my local instance. In the future, it will be called by other services. – Voldemort93 Jan 22 '20 at 09:59
  • @JohnHanley yes, I want to use OAuth token. Because if I'm not mistaken I need to generate Identity Token each time when I want to invoke function due to it's changed. I was hoping that OAuth will do it automatically. – Voldemort93 Jan 22 '20 at 10:11
  • @JoséSoní I want to only Service Account can invoke the function. For other callers, it should be Unauthorized. – Voldemort93 Jan 22 '20 at 10:13
  • There are three types of OAuth tokens: Access, Identity and Refresh. You must use an `Identity` token. Google has examples in the documentation. – John Hanley Jan 22 '20 at 14:40

3 Answers3

6

Your example code is generating an access token. Below is a real example that generates an identity token and uses that token to call a Cloud Functions endpoint. The Function needs to have the Cloud Function Invoker role for the service account being used for authorization.

import json
import base64
import requests

import google.auth.transport.requests
from google.oauth2.service_account import IDTokenCredentials

# The service account JSON key file to use to create the Identity Token
sa_filename = 'service-account.json'

# Endpoint to call
endpoint = 'https://us-east1-replace_with_project_id.cloudfunctions.net/main'

# The audience that this ID token is intended for (example Google Cloud Functions service URL)
aud = 'https://us-east1-replace_with_project_id.cloudfunctions.net/main'

def invoke_endpoint(url, id_token):
    headers = {'Authorization': 'Bearer ' + id_token}

    r = requests.get(url, headers=headers)

    if r.status_code != 200:
        print('Calling endpoint failed')
        print('HTTP Status Code:', r.status_code)
        print(r.content)
        return None

    return r.content.decode('utf-8')

if __name__ == '__main__':
    credentials = IDTokenCredentials.from_service_account_file(
            sa_filename,
            target_audience=aud)

    request = google.auth.transport.requests.Request()

    credentials.refresh(request)

    # This is debug code to show how to decode Identity Token
    # print('Decoded Identity Token:')
    # print_jwt(credentials.token.encode())

    response = invoke_endpoint(endpoint, credentials.token)

    if response is not None:
        print(response)
John Hanley
  • 74,467
  • 6
  • 95
  • 159
3

You need to get an identity token to be able to (http) trigger your cloud function AND
your service account needs to have at least role Cloud Functions Invoker.

(To keep it as safe as possible create a service account that can only invoke cloud functions, nothing else -> to avoid issues when your service account key file falls in the wrong hands.)

Then you can run the following:

import os
import google.oauth2.id_token
import google.auth.transport.requests
import requests

os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'service_account_key_file.json' 

google_auth_request = google.auth.transport.requests.Request()
cloud_functions_url = 'https://europe-west1-your_project_id.cloudfunctions.net/your_function_name'

id_token = google.oauth2.id_token.fetch_id_token(
    google_auth_request, cloud_functions_url)

headers = {'Authorization': f'Bearer {id_token}'}
response = requests.get(cloud_functions_url, headers=headers)

print(response.content)

This question + answers helped me also:
How can I retrieve an id_token to access a Google Cloud Function?

If you need an access token via python (instead of a identity token), check here:
How to get a GCP Bearer token programmatically with python

Sander van den Oord
  • 10,986
  • 5
  • 51
  • 96
0

The Error is because of calls not being Authenticated.

As the calls now are from your localhost this should work for you.

curl https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME \
  -H "Authorization: bearer $(gcloud auth print-identity-token)"

this will make the call with the account you have active on gcloud from your localhost.

The account making the call must have the role Cloud Functions Invoker

In the future just make sure that the service is calling this function has the role mentioned.

Soni Sol
  • 2,367
  • 3
  • 12
  • 23