Using Google Cloud Secret Manager with Cloud Functions for Firebase vs Cloud Run - google-cloud-functions

Google Cloud Secret Manager provides a simple and quick way to have access to its secrets with Cloud Functions for Firebase. Basically, you just set some secrets using its intuitive CLI (e.g. firebase functions:secrets:set SECRET_NAME), add a parameter called runWith (e.g. .runWith({ secrets: ["SECRET_NAME"] }) in your source code, and voila. This works even great when testing functions locally!
On the other hand, using SM on Cloud Run requires more steps like setting up services accounts, dependencies, and environment variables (doc). Although using the Google Console to configure secrets, there're still more steps involved.
As an example, here is what a node.js code for Cloud Run would look like (ref):
const {SecretManagerServiceClient} = require('#google-cloud/secret-manager');
const client = new SecretManagerServiceClient();
async function accessSecret(name, version='latest') {
try {
if (!process.env.GOOGLE_PROJECT_ID) {
throw 'Please set the GOOGLE_PROJECT_ID environment variable.';
}
const fullName = `projects/${process.env.GOOGLE_PROJECT_ID}/secrets/` +
`${name}/versions/${version}`;
const [response] = await client.accessSecretVersion({name: fullName});
const payload = response.payload.data.toString();
return payload;
}
catch (ex) {
console.log(ex.toString());
}
}
Is there a better and convenient way to use SM on a code for a Cloud Run project? Also wondering what is the performance impact of the two different approaches on both products.

I’m afraid that there are no other ways to use Secrete manager on cloud run other than following this documentation
Cloud Functions for Firebase has extended behavior of Firebase and integrated Firebase features. The simpler steps you mentioned to integrate secrets with cloud functions for firebase might be the feature added by the firebase.
And you can see that Cloud Functions for gcp somehow follows similar steps as cloud run on gcp.
Cloud run is not supported on Firebase. Hence, it doesn't have extended features like Cloud Functions for firebase.

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.

Application Default Credentials Not Working in Cloud Function to access Google Workspace API

I am trying to use the service account using Google Cloud Functions to access the Workspace Directory API. I am trying to use Application Default Credentials approach. Since the documentation doesn't mention any additional steps to be done in the Google Cloud Console side, I assume that Application Default Credentials (ADC) to the function is automatically passed to the cloud function from Google Cloud's Metadata server. The following code works perfectly in my local, emulated environment where I have set GOOGLE_APPLICATION_CREDENTIALS environment variable to point to the JSON credential file. However when I deploy the function to Google Cloud, I am getting the error "Not Authorized to access this resource/api". I have been searching and trying for days without any success. As an aside, before I stumbled upon this thread, I was using getCredentials() method of GoogleAuth to get the private_key to create JWT auth (for the "subject" property) and passing that auth to admin API call. Which again worked perfectly in my local environment but fails in the cloud environment because getCredentials() private_key is null, which is probably expected behavior. Any help is deeply appreciated. I am inexperienced with posting in Stackoverflow, if I need to follow any other protocol, please advise.
export const listUsers = functions.https.onCall((data, context) => {
return new Promise(async (resolve, reject) => {
const envAuth = new GoogleAuth({
scopes: ["https://www.googleapis.com/auth/admin.directory.user.readonly"],
clientOptions: {
subject: "admin#mydomain.com",
},
});
const client = await envAuth.getClient();
const service = google.admin({version: "directory_v1", auth: client});
try {
const response = await service.users.list({
customer: "MYCUSTOMER_ID",
});
resolve(response);
} catch (err) {
reject(err);
}
});
});
When you run a Cloud Function without authenticating via a key file (the json credential file that has the key to allow you to impersonate/authenticate as a different user), the function runs as the default application credentials for the project (<YOUR_CLOUD_PROJECT_ID>#appspot.gserviceaccount.com). You don't have to do anything special, no authentication calls -- it runs with the permissions of this service account by default.
When you run the code locally, it takes on the user authentication of whatever key file you have locally (and have referenced in the "GOOGLE_APPLICATION_CREDENTIALS" environment variable on your machine).
Without full security information on your environment, it's hard to know for sure... But, I'm guessing that you're trying to access resources that your personal Google Account has proper access; however, the default service account for you Cloud Functions Project does not.
So, couple of routes to troubleshoot and/or fix the issue:
Give the default service account access rights to Workspace resource(s) you're attempting to access.
Use the JSON key file you setup locally already, to have the Cloud Function run as the same user as is happening when you run locally.
Essentially do a hybrid where you create a new service account that ONLY has the permissions you want (instead of using the default service account or your personal user, both of which might have far more permissions then desired for safety/security), use a key file to run the Cloud Function under that identity, and only give the desired permissions to that service account.

Cloud Function with Cloud Function Invoker rights gets 403 status

I have two Cloud Functions: a frontend and an API. I have added a frontend service account to the frontend Cloud Function that has Cloud Function Invoker rights:
I have also explicitly added this service account as a permission to the API Cloud Function, yet I am still getting 403 Not Authorized statuses.
const categories = await this.$axios.$get("/api/v1/categories/", {
params: { hasParent: 0 },
});
You must add the authorization header to make it work! It's not out of the box!
But, the cloud functions doc is bad to explain that. Use the Cloud Run documentation instead (the 2 services use the same underlying infrastructure and the principle are the same, the doc is simply easier to use on Cloud Run!!)

security doubts about google cloud functions

I've been reading a lot of questions here about security regarding cloud functions (HTTP triggered) and I also read google's official docs but I couldn't find a clear answer for some questions, so I need help.
Please note that this question is about google's cloud functions made from Google cloud console, nothing to do with firebase.
It's possible to make a function "callable" just from my website? I tried to use cors policy but I have it clear that cors have nothing to do with security, so I'm a little bit worried about how I can keep my cloud function "callable" just from my domain.
On the other hand I created a service account on Google Cloud Platform and I'm trying to use it as an invoker. I have set my service account as invoker but how do I use that on my server?
CASE: I'm creating a log for my web, so I created a cloud function that I call every time someone accesses my site: (I'm using Google Tag Manager server-side).
const sendHttpRequest = require("sendHttpRequest");
const postBody = {
testing : true
}
//Calls cloud function
sendHttpRequest(
"<CLOUD FUNTION TRIGGER ADDRESS>",
(statusCode, headers, body) => {
setResponseStatus(200);
setResponseBody("done");
},
{
headers: { "content-type": "application/json; charset=utf-8", "Origin" : "https://example.com" },
method: "POST",
},
postBody
);
}
I would like to know how I can be sure that this cloud function can only be invoked by my server.
Thanks in advance!
Yes, it's possible.
See Authenticating for Invocation.
The second paragraph provides a good synopsis of why this has some complexity.
You're correct in using a Service Account. Service Accounts are used by software. User accounts are used by humans.
It's unclear where your website is running but it will need to generate an identity token (aka JWT) in order to securely invoke the remote Cloud Function.
See the developer testing example in which an identity token is provided by the Cloud SDK (gcloud) using gcloud auth print-identity-token and then used as the Authorization header value with curl.
That's what your website needs to replicate.
The page recommends (correctly) considering using one of Google SDKs to generating tokens programmatically, because the alternative is gnarly and prone to error.
Unless your website is also running on GCP, you can't use the metadata service .

How do I configure project and gcloud to connect to Datastore?

I'm unable to get my Google Compute instance to speak to the Datastore (within the same project).
I believe I've set everything up correctly:
Google Compute instance has the full API scope
All the relevant APIs have been enabled in the project
Billing is enabled in the project
The tutorials claim that I won't even need to have a service account to use the API, but since it didn't work I also tried setting up a service account and put the key file on my instance. Still no luck.
Here's the code I'm trying to run (I created a Test entity in the Datastore of the project, and I can successfully look it up using Google's API explorer with an OAuth2 token for my account):
const gcloud = require('gcloud')({
projectId: 'roger-web-client',
keyFilename: './roger-web-client-8d1fbd8baae2.json',
});
const dataset = gcloud.datastore.dataset();
dataset.get(dataset.key(['Test', 5629499534213120]), (error, entity) => {
console.log(error || entity);
});
This results in the error 403 Forbidden. If I comment out keyFilename, I get 401 Unauthorized instead, which seems to imply the magic authorization on Google Compute instances isn't working for me.
Ideas?