August 24, 2024
@albertdbio
Albert Martinez
@albertdbio

Calling Cloud Functions from Cloud Run

A simple guide to GCP authentication in production and development environments

One of the best parts of Google's Cloud Platform is it's authentication strategy.

GCP's Application Default Credentials (ADC) simplifies the process of authenticating with GCP services both in development and production environments.

But sometimes the documentation is hard to navigate.

The following guide will details the steps to setting up ADC locally and it's usage in production.

Setting up ADC

Setting up ADC locally is quite simple and it will allow us to understand how it works behind the scenes.

We begin with installing the gcloud CLI by downloading the installer from the Google Cloud website.

Once installed, we can run the following command to use ADC:

gcloud auth application-default login

This adds credentials to the application_default_credentials.json file under ~/.config/gcloud/.

Google Cloud's client libraries will automatically use these credentials when making requests to GCP services.

For example, here is a simple Cloud Function that saves a file to a Cloud Storage bucket:

save-file.ts
import { Storage } from "@google-cloud/storage"
function saveFile() {
const storage = new Storage()
const bucket = storage.bucket("my-bucket")
const file = bucket.file("my-file")
await file.save("Hello, world!")
}

Notice how we don't have to pass any credentials to the Storage class.

This works as long as the account you logged in with has permissions to access the bucket.

If it doesn't, you can set the credentials explicitly.

Setting Credentials

save-file.ts
import { Storage } from "@google-cloud/storage"
function saveFile() {
const storage = new Storage({
credentials: {
client_email: process.env.GCP_CLIENT_EMAIL,
private_key: process.env.GCP_PRIVATE_KEY
},
})
const bucket = storage.bucket("my-bucket")
const file = bucket.file("my-file")
await file.save("Hello, world!")

GCP_CLIENT_EMAIL and GCP_PRIVATE_KEY are environment variables we set in our .env file.

We can find the correct GCP_CLIENT_EMAIL to use by looking at which service accounts have access to the bucket. This can be found under the permissions tab of the bucket:

bucket-permissions

As for the GCP_PRIVATE_KEY, we need to create a private key for the service account we want to use.

This can be done under the Keys tab of the service account:

service-account-keys

Since we plan to call a Cloud Function from a Cloud Run service, we will use the default service account for App Engine.

Formatting the Private Key

One thing to note is that we need to format the private key as a multiline string.

The private key we generated is a string which contains the characters \ and n.

We want to replace them with the actual newline character \n.

save-file.ts
import { Storage } from "@google-cloud/storage"
function saveFile() {
const storage = new Storage({
credentials: {
client_email: process.env.GCP_CLIENT_EMAIL,
private_key: process.env.GCP_PRIVATE_KEY!.split(String.raw`\n`).join('\n')
},
})
const bucket = storage.bucket("my-bucket")
const file = bucket.file("my-file")
await file.save("Hello, world!")
}

Setting the Project

The last step is to set the project in which the bucket resides.

If the bucket is in your production environment, use the name of the production project.

save-file.ts
import { Storage } from "@google-cloud/storage"
function saveFile() {
const storage = new Storage({
credentials: {
client_email: process.env.GCP_CLIENT_EMAIL,
private_key: process.env.GCP_PRIVATE_KEY!.split(String.raw`\n`).join('\n')
},
projectId: "my-production-project",
})
const bucket = storage.bucket("my-bucket")
const file = bucket.file("my-file")
await file.save("Hello, world!")
}

Recap

save-file.ts
import { Storage } from "@google-cloud/storage"
function saveFile() {
const storage = new Storage({
credentials: {
client_email: process.env.GCP_CLIENT_EMAIL,
private_key: process.env.GCP_PRIVATE_KEY!.split(String.raw`\n`).join('\n')
},
projectId: "my-production-project",
})
const bucket = storage.bucket("my-bucket")
const file = bucket.file("my-file")
await file.save("Hello, world!")
}

We are now able to save a file to a production bucket from our local machine.

It's important to note that we only needed to set the credentials and project because the account we used when running gcloud auth application-default login didn't have access to the production bucket.

However, when we deploy our Cloud Function or Cloud Run service to production we may not need to set the credentials and project at all.

Deploying to Production

As long as the service account of the Cloud Function or Cloud Run service has access to the bucket, we can deploy the service without setting the credentials and project.

save-file.ts
import { Storage } from "@google-cloud/storage"
function saveFile() {
// Uncomment this when running on your local machine
// const storage = new Storage({
// credentials: {
// client_email: process.env.GCP_CLIENT_EMAIL,
// private_key: process.env.GCP_PRIVATE_KEY!.split(String.raw`\n`).join('\n')
// },
// projectId: "my-production-project",
// })
const storage = new Storage()
const bucket = storage.bucket("my-bucket")
const file = bucket.file("my-file")
await file.save("Hello, world!")
}

We have now seen how to use ADC to authenticate with GCP services both in development and production.

The beauty of ADC is that it handles the authentication for us behind the scenes.

We only need to set the credentials manually when we need to access a service that our account doesn't have access to or that resides in a different project.