Accessing Google Cloud Function via API Key - google-cloud-functions

I created a Google Cloud Function from my ML Models. It works fine with the google "Testing" on the GCP site of the function:
Screenshot of the testing
I have the function hosted 2 times, one time with authentication (Google IAM) and a second time non-authenticated
authentication modi
if I now want to invoke the function e.g. in postman the version without authentication works fine.
But with authentication things it gets out of hand to figure out how to accomplish that.
How can I achieve access to the cloud function with an restricted API key?`

You can't invoke your function directly with an API Key. You need to implement a proxy layer that check your API Key and perform a request with OAuth2 granted identity token. To achieve this, you can use Cloud Endpoint or its brand fresh serverless implementation API Gateway. I wrote an article on Cloud Endpoint and you can reuse it on API Gateway.
If it's just for Postman and your tests, you can generate a token with the GCLOUD CLI
gcloud auth print-identity-token
Copy the result and add it to the header of your request
Authorization: Bearer <token>
It is valid for 1H. Perform your tests, when it is expired, generate a new one and continue.
I also wrote a small tool for this. Perform a precall with Postman to get the token and then use it in your request as previously described

Recently Google added in beta a new Api Gateway, which will hide your google function declarations and provide an HTTP authentication using API KEY
https://cloud.google.com/api-gateway/docs/quickstart-console#securing_access_by_using_an_api_key
Doing that, you can create an authentication between the client and the gateway using the API Key and the authentication between the gateway and the google function, can be done using a normal service account

To make authenticated call to cloud function using postman, you need to jwt_token.
First create service account
Give Cloud function invoker access to this SA
Generate JWT
import time
import google.auth.crypt
import google.auth.jwt
sa_keyfile = 'path_of_service_account'
iss = 'email_address_of_service_account'
aud = 'function_url'
iat = int(time.time())
exp = iat + 3600
def generate_jwt():
"""Generates a signed JSON Web Token using a Google API Service Account."""
payload = {"iat": iat, "exp": exp, "iss": iss, "aud": aud, "sub": iss, "email": iss}
signer = google.auth.crypt.RSASigner.from_service_account_file(sa_keyfile)
jwt = google.auth.jwt.encode(signer, payload)
return jwt
if __name__ == '__main__':
signed_jwt = generate_jwt()
print(signed_jwt.decode()+'\n')

Related

Calling Firebase Hosting API from a Firebase Cloud Function

I have a Firebase (node.js) cloud function that pulls in some data from my app's Firestore database and builds some static content for the web. I'd like that same cloud function to deploy the static content to Firebase hosting via the Firebase Hosting API, creating a static portion of my site with user generated content.
I understand the general flow thanks to the somewhat clear walkthrough, but am stuck on the first step: getting an access token to call the API. Obviously I'm not going to insecurely put my service account key in the cloud function itself, so the example in the walkthrough doesn't apply. And as I understand it, Firebase cloud functions are already associated with a service account, so presumably there's some way to get an access token to call other Google Cloud services from a cloud function.
So how do I get an access token to call the hosting API from a Cloud Function?
There are some red flags that make me think this isn't possible. For example, all of the uses cases in the walkthrough allude to other server environments, as opposed to Google Cloud environments. And yet, this use case is the third bullet in the use case list in the walkthrough.
I've searched extensively here and elsewhere for some guidance, but aren't finding anything. There are some older questions about accessing hosted files from a cloud function that aren't relevant. This promising question from 5 years ago about this exact use case only has dead ends.
You can use the google-auth-library package in Cloud Functions to a get a token as shown below:
import { GoogleAuth } from "google-auth-library";
const token = await new GoogleAuth({
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
}).getAccessToken();
If you use Firebase Admin SDK in the Cloud Functions, then you can get an access token of the default service account as shown below (do ensure the service account has required permissions):
import { initializeApp } from "firebase-admin/app";
const admin = initializeApp();
const token = await admin.options.credential?.getAccessToken();
// ^ Google OAuth2 access token object used to authenticate with Firebase services.

Google Cloud Function :: Service account :: JWT token and Bearer token

I have a Google Cloud Function. I also have a web application. I want to authenticate requests to the cloud function by using a service account.
I have the json key file.
I know that I have to follow https://cloud.google.com/functions/docs/securing/authenticating#service-to-function. But that is leading me to an IAP page that does not apply to google cloud functions.
Another similar instructions are found in https://developers.google.com/identity/protocols/oauth2/service-account
But if I am following the python library code, I end up with the sample code there :
import googleapiclient.discovery
sqladmin = googleapiclient.discovery.build('sqladmin', 'v1beta3', credentials=credentials)
response = sqladmin.instances().list(project='exciting-example-123').execute()
This does not directly relate to invoking a cloud function.
This question's answer somewhat deals with my requirement but is using a Call API which is only suitable for testing.
Also, I want to expose this API to multiple applications using another tech like .net. So I believe the best option for me will be to use the HTTP method (given on the same page):
https://developers.google.com/identity/protocols/oauth2/service-account#httprest
But whatever I do I am unable to get the signature right.
Any help to get this sorted will be highly appreciated as I am stuck on this for the past few days.
You can use the Google auth library like this
from google.oauth2.id_token import fetch_id_token
from google.auth.transport import requests
audience="my_audience"
r = requests.Request()
token=fetch_id_token(r,audience)
print(token)
The fetch_id_token method will use the default credentials
The service account key file defined in the environment variable GOOGLE_APPLICATION_CREDENTIALS
The service account loaded in the Google Cloud environment
For now, I followed this answer in PHP
In the claims section, I removed the scope. Instead added a claim of target_audience.
"target_audience" => "google-function-http-trigger"
the cloud function http trigger will look like https://us-central1-test-project-name.cloudfunctions.net/function-name",
This will give the required assertion key.
Then I follow https://developers.google.com/identity/protocols/oauth2/service-account#httprest to get the id_token
Then with the id_token as the bearer token we can call the cloud function.
please note that the token expires depending on the time set in the "exp" claim. Once expired you have to redo the steps to generate the new id_token
I want to authenticate requests to the cloud function by using a service account.
I am not sure I understand the context correctly, but I would try to assign a roles/cloudfunctions.invoker IAM role to that service account (which is used to run your code in the web application) - see Cloud Functions IAM Roles .
In that case a code under that service account "Can invoke an HTTP function using its public URL"
I reckon no json keys are required in this case.

Connecting Google add-on to google vm instance

I am developing an add-on for google drive.
As a part of the functionality of this add-on, I would like to incorporate a google vm server that performs some processing on a user's google drive files (i.e. you click on a file through the add-on, send a request with a download link to the server, the server downloads the file, then finally responds to the request with some helpful information about the file). Both the apps script for the add-on and the vm instance are connected to the same google "project."
I am struggling with google's OAuth2.0 system and how I can connect the authorization of the add-on and the vm instance together.
Currently, when users open the add-on for the first time, they are brought to the authorization screen like so .
Once they authorize, my add on has access to all the expected scopes, including read access to google drive files.
Now I want my server to have access to them as well. Unfortunately, I do not understand how to do this.
I have tried simply requesting the url returned from file.getDownloadUrl() in python. While the request returns a status code of 200, I cannot seem to get the file to download.
I have also looked into the Google Drive API for python (I am running a flask server). Unfortunately, it appears that I need an entirely new authorization flow to make it work.
Any clarity on this issue would be greatly appreciated. Frankly, I find google's documentation on this matter very scattered and confusing. So, even knowing the right place to look would extremely helpful.
Much Thanks!
EDIT
I am adding some additional code to help provide some clarity. This is currently how I make a request to my server from the add-on:
var route = http://exampleurl.com/process
var data = {
'oAuthToken': ScriptApp.getOAuthToken(),
'stateToken': ScriptApp.newStateToken().withTimeout(120).createToken(),
'fileId': e.parameters.fileId,
'fileType': e.parameters.fileMimeType
};
var options = {
'method' : 'post',
'payload' : data
};
var response = UrlFetchApp.fetch(route, options);
This code successfully sends information to my vm instance running my server.
Now, I need to authorize the server to download the file specified by fileId.
When developing, I closely followed this tutorial to set up OAuth2.0 access to the Drive API. Here are two key routes:
#app.route('/google/login')
#no_cache
def login():
session = OAuth2Session(CLIENT_ID, CLIENT_SECRET,
scope=AUTHORIZATION_SCOPE,
redirect_uri=AUTH_REDIRECT_URI)
uri, state = session.create_authorization_url(AUTHORIZATION_URL)
flask.session[AUTH_STATE_KEY] = state
flask.session.permanent = True
return flask.redirect(uri, code=302)
#app.route('/google/auth')
#no_cache
def google_auth_redirect():
req_state = flask.request.args.get('state', default=None, type=None)
if req_state != flask.session[AUTH_STATE_KEY]:
response = flask.make_response('Invalid state parameter', 401)
return response
session = OAuth2Session(CLIENT_ID, CLIENT_SECRET,
scope=AUTHORIZATION_SCOPE,
state=flask.session[AUTH_STATE_KEY],
redirect_uri=AUTH_REDIRECT_URI)
oauth2_tokens = session.fetch_access_token(
ACCESS_TOKEN_URI,
authorization_response=flask.request.url)
flask.session[AUTH_TOKEN_KEY] = oauth2_tokens
return flask.redirect(BASE_URI, code=302)
Is there a way to plug in the two tokens I generate from the add-on into this Oauth flow? It appears that Google isn't anticipating this setup given that I am required to provide a redirect URL, which wouldn't make much sense in the case of my add-on/server tech stack.
Currently, you can successfully send the access token from the Apps Script project (retrieved with getOAuthToken()), to the flask server.
Since you already got the access token, you don't need to go through all the OAuth process as defined here (use the application credentials to request the access token, provide user consent, redirect, etc.). Sending the access token through the API request is actually the last step in the OAuth flow.
You just need to use the token to build the service, and the server will be able to access the file.
Using access token to build service:
from googleapiclient.discovery import build
import google.oauth2.credentials
ACCESS_TOKEN = requestBody['oAuthToken'] # Data coming from Apps Script via UrlFetch
creds = google.oauth2.credentials.Credentials(ACCESS_TOKEN)
drive_service = build('drive', 'v3', credentials=creds) # Build Drive service
Using Drive service to download files:
After you've build the Drive service to access the API, you can use this code sample to download Google Documents, or this one for other files (both examples include a tab with a Python sample).
Note:
You might need to Install the Google Client Library if you haven't done so.
Reference:
Using OAuth 2.0 to Access Google APIs

Google cloud functions http authentication

I am new to google cloud functions and try to restrict access to my function by only requests from dialogflow webhooks. I see two options in gcloud console: allow unauthenticated requests and restrict by user accounts. I don't understand how to implement that authentication. Dialogflow webhooks has options to set http headers that sets in webhook requests. But gcloud console hasn't interface/options to obtain any data that I can write as http authentication header. So I see only option implement authetication flow in cloud function, but in that way why google added option to restrict access by http authentication. Can anyone give me an example step-by-step example how to obtain http headers names and data needed to implement http authentication on cloud functions from dialogflow webhooks?
There isn't built in authentication, you have to perform it by yourselves. You have some guidance here in the Google Cloud Documentation
In summary, set your function public (allow unauthenticated) and perform the check in your code.

how to get token from 1 api and pass the token to authentication of another api in azure api gateway

I have 2 APIs in azure API gateway.
Generates a token from 1 API.
Use that token in authentication in another API
How can I combine the above APIs into 1 or connect both in 1 flow?
Note: Both the APIs are on different servers
If the backend url is different, its better to configure as two APIs.
Please follow the steps below.
Create Auth API and set the auth server as backend url
Create your API which consumes and set the backendurl.
Create a product which links these 2 apis. (So the subscription key will be same for the both apis.)
The user can call the auth url and get the token
Add it as a header to the next api.
If you only want to configure single api, follow below steps.
Create the api with auth endpoint in it.
Use the <set-backend-service/> for the auth url to set the differnt backend server.
see more on https://learn.microsoft.com/en-us/azure/api-management/api-management-transformation-policies#SetBackendService
create a product with single api
Here the url prefix will be same and functionality will be working as expected.